From a9ce927465f044cb1be22f96c2bf9752c23d15b4 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Sat, 6 Oct 2012 23:14:15 -0400
Subject: [PATCH 01/40] Fixes #744, allow null in object, last, first, and
size.
---
test/arrays.js | 6 ++++++
test/collections.js | 2 ++
underscore.js | 4 ++++
3 files changed, 12 insertions(+)
diff --git a/test/arrays.js b/test/arrays.js
index a757ced67..32252a3f5 100644
--- a/test/arrays.js
+++ b/test/arrays.js
@@ -14,6 +14,8 @@ $(document).ready(function() {
equal(result.join(','), '1,1', 'works well with _.map');
result = (function() { return _.take([1,2,3], 2); })();
equal(result.join(','), '1,2', 'aliased as take');
+
+ equal(_.first(null), undefined, 'handles nulls');
});
test("rest", function() {
@@ -47,6 +49,8 @@ $(document).ready(function() {
equal(result, 4, 'works on an arguments object');
result = _.map([[1,2,3],[1,2,3]], _.last);
equal(result.join(','), '3,3', 'works well with _.map');
+
+ equal(_.last(null), undefined, 'handles nulls');
});
test("compact", function() {
@@ -136,6 +140,8 @@ $(document).ready(function() {
var stooges = {moe: 30, larry: 40, curly: 50};
ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object');
+
+ ok(_.isEqual(_.object(null), {}), 'handles nulls');
});
test("indexOf", function() {
diff --git a/test/collections.js b/test/collections.js
index efc5723d4..7dce44ba5 100644
--- a/test/collections.js
+++ b/test/collections.js
@@ -411,6 +411,8 @@ $(document).ready(function() {
equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
equal(_.size('hello'), 5, 'can compute the size of a string');
+
+ equal(_.size(null), 0, 'handles nulls');
});
});
diff --git a/underscore.js b/underscore.js
index 1ebe2671b..43b4e407d 100644
--- a/underscore.js
+++ b/underscore.js
@@ -369,6 +369,7 @@
// Return the number of elements in an object.
_.size = function(obj) {
+ if (obj == null) return 0;
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
};
@@ -379,6 +380,7 @@
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
+ if (array == null) return void 0;
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
@@ -393,6 +395,7 @@
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
+ if (array == null) return void 0;
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
@@ -491,6 +494,7 @@
// pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values.
_.object = function(list, values) {
+ if (list == null) return {};
var result = {};
for (var i = 0, l = list.length; i < l; i++) {
if (values) {
From 270c0569737bc1661d8cc478cabe08e4ef1c9d28 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Mon, 8 Oct 2012 09:58:24 -0400
Subject: [PATCH 02/40] Fixes #819 -- explain use of chaining syntax.
---
index.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/index.html b/index.html
index 71b1f5a36..9a126159d 100644
--- a/index.html
+++ b/index.html
@@ -1444,6 +1444,8 @@
Utility Functions
Invokes the given iterator function n times. Each invocation of
iterator is called with an index argument.
+
+ Note: this example uses the chaining syntax.
Sorts a list into groups and returns a count for the number of objects
in each group.
- Similar to groupBy, but instead of returning a list of values,
+ Similar to groupBy, but instead of returning a list of values,
returns a count for the number of values in that group.
object_.object(list, [values])
- Converts arrays into objects. Pass either a single list of
+ Converts arrays into objects. Pass either a single list of
[key, value] pairs, or a list of keys, and a list of values.
@@ -845,8 +846,8 @@
Array Functions
or -1 if value is not present in the array. Uses the native
indexOf function unless it's missing. If you're working with a
large array, and you know that the array is already sorted, pass true
- for isSorted to use a faster binary search ... or, pass a number as
- the third argument in order to look for the first matching value in the
+ for isSorted to use a faster binary search ... or, pass a number as
+ the third argument in order to look for the first matching value in the
array after the given index.
@@ -859,7 +860,7 @@
Array Functions
Returns the index of the last occurrence of value in the array,
or -1 if value is not present. Uses the native lastIndexOf
- function if possible. Pass fromIndex to start your search at a
+ function if possible. Pass fromIndex to start your search at a
given index.
@@ -1122,7 +1123,7 @@
Object Functions
invert_.invert(object)
Returns a copy of the object where the keys have become the values
- and the values the keys. For this to work, all of your object's values
+ and the values the keys. For this to work, all of your object's values
should be unique and string serializable.
@@ -1442,7 +1443,7 @@
Utility Functions
times_.times(n, iterator, [context])
- Invokes the given iterator function n times. Each invocation of
+ Invokes the given iterator function n times. Each invocation of
iterator is called with an index argument.
Note: this example uses the chaining syntax.
@@ -1499,6 +1500,16 @@
Utility Functions
_.escape('Curly, Larry & Moe');
=> "Curly, Larry & Moe"
+
+ unescape_.unescape(string)
+
+ The opposite of escape, replaces escaped
+ HTML entities with with their unescaped counterparts.
+
+
+_.escape('Curly, Larry & Moe');
+=> "Curly, Larry & Moe"
- For backwards compatibility, returned to pre-1.4.0 behavior when
- passing null to iteration functions. They now become no-ops
+ For backwards compatibility, returned to pre-1.4.0 behavior when
+ passing null to iteration functions. They now become no-ops
again.
- Added a pairs function, for turning a JavaScript object
- into [key, value] pairs ... as well as an object
- function, for converting an array of [key, value] pairs
+ Added a pairs function, for turning a JavaScript object
+ into [key, value] pairs ... as well as an object
+ function, for converting an array of [key, value] pairs
into an object.
@@ -1783,7 +1794,7 @@
Change Log
in a list that match a certain criteria.
- Added an invert function, for performing a simple inversion
+ Added an invert function, for performing a simple inversion
of the keys and values in an object.
@@ -1814,11 +1825,11 @@
Change Log
functions. Use a for loop instead (or better yet, an object).
- The min and max functions may now be called on
+ The min and max functions may now be called on
very large arrays.
- Interpolation in templates now represents null and
+ Interpolation in templates now represents null and
undefined as the empty string.
unescape_.unescape(string)
- The opposite of escape, replaces escaped
- HTML entities with with their unescaped counterparts.
+ The opposite of escape, replaces
+ &, <, >,
+ ", ', and /
+ with their unescaped counterparts.
_.escape('Curly, Larry & Moe');
From b4bd1c5a6183e605c86909b74445c3c6919bea07 Mon Sep 17 00:00:00 2001
From: "Nikolay S. Frantsev"
Date: Tue, 23 Oct 2012 12:43:23 +0300
Subject: [PATCH 08/40] Native-like names in documentation for _.all and _.any
methods
---
index.html | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/index.html b/index.html
index bfea75d4d..98659c59e 100644
--- a/index.html
+++ b/index.html
@@ -198,8 +198,8 @@
- all_.all(list, iterator, [context])
- Alias: every
+
+ every_.every(list, iterator, [context])
+ Alias: all
Returns true if all of the values in the list pass the iterator
truth test. Delegates to the native method every, if present.
- any_.any(list, [iterator], [context])
- Alias: some
+
+ some_.some(list, [iterator], [context])
+ Alias: any
Returns true if any of the values in the list pass the
iterator truth test. Short-circuits and stops traversing the list
@@ -529,7 +529,7 @@
From d88ea4d444e7cf9ab2d8303652e20bb6394fe1f4 Mon Sep 17 00:00:00 2001
From: Elias Zamaria
Date: Sun, 4 Nov 2012 18:40:00 -0800
Subject: [PATCH 09/40] Juggle arguments in `_.uniq` to allow an argument
signature of `array, iterator, context`
Issue 832: Juggle arguments in `_.uniq` to allow an argument signature
of `array, iterator, context`
---
test/arrays.js | 2 ++
underscore.js | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/test/arrays.js b/test/arrays.js
index 32252a3f5..f53074d67 100644
--- a/test/arrays.js
+++ b/test/arrays.js
@@ -91,6 +91,8 @@ $(document).ready(function() {
var iterator = function(value) { return value.name; };
equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
+ equal(_.map(_.uniq(list, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator without specifying whether array is sorted');
+
var iterator = function(value) { return value +1; };
var list = [1, 2, 2, 3, 4, 4];
equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
diff --git a/underscore.js b/underscore.js
index db899df47..b326ffed9 100644
--- a/underscore.js
+++ b/underscore.js
@@ -439,6 +439,11 @@
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator, context) {
+ if (_.isFunction(isSorted)) {
+ context = iterator;
+ iterator = isSorted;
+ isSorted = false;
+ }
var initial = iterator ? _.map(array, iterator, context) : array;
var results = [];
var seen = [];
From c5c9149f2938f0435fa1a0db87ee26dc2901fa7e Mon Sep 17 00:00:00 2001
From: Kim Joar Bekkelund
Date: Mon, 5 Nov 2012 11:53:24 +0100
Subject: [PATCH 10/40] Remove local variable from `contains`
---
underscore.js | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/underscore.js b/underscore.js
index db899df47..e839f317e 100644
--- a/underscore.js
+++ b/underscore.js
@@ -213,13 +213,11 @@
// Determine if the array or object contains a given value (using `===`).
// Aliased as `include`.
_.contains = _.include = function(obj, target) {
- var found = false;
- if (obj == null) return found;
+ if (obj == null) return false;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
- found = any(obj, function(value) {
+ return any(obj, function(value) {
return value === target;
});
- return found;
};
// Invoke a method (with arguments) on every item in a collection.
From 9404ac9597e1367c619b1e03c7780192146cdd73 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Sun, 11 Nov 2012 21:57:14 -0500
Subject: [PATCH 11/40] Fixes #856 -- use local var for arguments length check.
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index 65b59c0cd..2786edbb3 100644
--- a/underscore.js
+++ b/underscore.js
@@ -130,7 +130,7 @@
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
- return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var length = obj.length;
if (length !== +length) {
From 3148c4f1636b6f93f313607b72ce5fd8a25f47ab Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Sun, 11 Nov 2012 22:04:08 -0500
Subject: [PATCH 12/40] Fixes #854 -- return values for non-numeric
comparisons.
---
test/collections.js | 2 ++
underscore.js | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/test/collections.js b/test/collections.js
index e089626df..32e27f2fc 100644
--- a/test/collections.js
+++ b/test/collections.js
@@ -268,6 +268,7 @@ $(document).ready(function() {
equal(-Infinity, _.max({}), 'Maximum value of an empty object');
equal(-Infinity, _.max([]), 'Maximum value of an empty array');
+ equal(_.max({'a': 'a'}), -Infinity, 'Maximum value of a non-numeric collection');
equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
});
@@ -280,6 +281,7 @@ $(document).ready(function() {
equal(Infinity, _.min({}), 'Minimum value of an empty object');
equal(Infinity, _.min([]), 'Minimum value of an empty array');
+ equal(_.min({'a': 'a'}), Infinity, 'Minimum value of a non-numeric collection');
var now = new Date(9999999999);
var then = new Date(0);
diff --git a/underscore.js b/underscore.js
index 1ac7c1abe..d94a98537 100644
--- a/underscore.js
+++ b/underscore.js
@@ -253,7 +253,7 @@
return Math.max.apply(Math, obj);
}
if (!iterator && _.isEmpty(obj)) return -Infinity;
- var result = {computed : -Infinity};
+ var result = {computed : -Infinity, value: -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
@@ -267,7 +267,7 @@
return Math.min.apply(Math, obj);
}
if (!iterator && _.isEmpty(obj)) return Infinity;
- var result = {computed : Infinity};
+ var result = {computed : Infinity, value: Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
From 187bc1dae59237e057175d1438f0c24bd4d48ef6 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Sun, 11 Nov 2012 22:08:05 -0500
Subject: [PATCH 13/40] Fixes #647 -- removes old closure compiler
pessimization.
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index d94a98537..bb4abcc62 100644
--- a/underscore.js
+++ b/underscore.js
@@ -61,7 +61,7 @@
}
exports._ = _;
} else {
- root['_'] = _;
+ root._ = _;
}
// Current version.
From c42a3e6772f36ee3b6372ce5b0b98c9fbdda158a Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Sun, 11 Nov 2012 22:15:41 -0500
Subject: [PATCH 14/40] Fixes #838 -- use hasOwnProperty when looping through
_.where attrs, for healthy paranoia's sake
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index bb4abcc62..e51851404 100644
--- a/underscore.js
+++ b/underscore.js
@@ -239,7 +239,7 @@
if (_.isEmpty(attrs)) return [];
return _.filter(obj, function(value) {
for (var key in attrs) {
- if (attrs[key] !== value[key]) return false;
+ if (_.has(attrs, key) && attrs[key] !== value[key]) return false;
}
return true;
});
From 0a2adcb0d19b42b61e8ab63d3a5dee6ef2bcce48 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Mon, 19 Nov 2012 13:37:43 -0500
Subject: [PATCH 15/40] Fixes #864 -- add a default iterator to groupBy and
countBy
---
test/collections.js | 10 ++++++++++
underscore.js | 2 +-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/test/collections.js b/test/collections.js
index 32e27f2fc..2e684056f 100644
--- a/test/collections.js
+++ b/test/collections.js
@@ -348,6 +348,11 @@ $(document).ready(function() {
var array = [{}];
_.groupBy(array, function(value, index, obj){ ok(obj === array); });
+
+ var array = [1, 2, 1, 2, 3];
+ var grouped = _.groupBy(array);
+ equal(grouped['1'].length, 2);
+ equal(grouped['3'].length, 1);
});
test('countBy', function() {
@@ -372,6 +377,11 @@ $(document).ready(function() {
var array = [{}];
_.countBy(array, function(value, index, obj){ ok(obj === array); });
+
+ var array = [1, 2, 1, 2, 3];
+ var grouped = _.countBy(array);
+ equal(grouped['1'], 2);
+ equal(grouped['3'], 1);
});
test('sortedIndex', function() {
diff --git a/underscore.js b/underscore.js
index e51851404..3771cb422 100644
--- a/underscore.js
+++ b/underscore.js
@@ -316,7 +316,7 @@
// An internal function used for aggregate "group by" operations.
var group = function(obj, value, context, behavior) {
var result = {};
- var iterator = lookupIterator(value);
+ var iterator = lookupIterator(value || _.identity);
each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj);
behavior(result, key, value);
From 26a30551f912f8180e6c2381d0eae4b24259fb70 Mon Sep 17 00:00:00 2001
From: David Glasser
Date: Mon, 19 Nov 2012 15:53:42 -0800
Subject: [PATCH 16/40] toArray: only call Array.prototype.slice on actual
arrays.
Specifically, this fixes _.toArray on NodeList objects on IE8, which worked in
Underscore 1.3.3 but throws "JScript object expected" in 1.4.0.
---
test/collections.js | 3 +++
underscore.js | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/test/collections.js b/test/collections.js
index 2e684056f..2cb3f6b67 100644
--- a/test/collections.js
+++ b/test/collections.js
@@ -418,6 +418,9 @@ $(document).ready(function() {
var numbers = _.toArray({one : 1, two : 2, three : 3});
equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
+
+ // _.toArray on a NodeList should not throw.
+ ok(_.isArray(_.toArray(document.childNodes)));
});
test('size', function() {
diff --git a/underscore.js b/underscore.js
index 3771cb422..647e62aa2 100644
--- a/underscore.js
+++ b/underscore.js
@@ -358,7 +358,8 @@
// Safely convert anything iterable into a real, live array.
_.toArray = function(obj) {
if (!obj) return [];
- if (obj.length === +obj.length) return slice.call(obj);
+ if (_.isArray(obj)) return slice.call(obj);
+ if (obj.length === +obj.length) return _.map(obj, _.identity);
return _.values(obj);
};
From 7fb14945eef293cb1b874de708a6c54b5448b63e Mon Sep 17 00:00:00 2001
From: Ore Landau
Date: Wed, 21 Nov 2012 11:11:49 +0200
Subject: [PATCH 17/40] Updated an old link to Prototype.js' API docs.
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 98659c59e..7010ee333 100644
--- a/index.html
+++ b/index.html
@@ -331,7 +331,7 @@
Underscore is a
utility-belt library for JavaScript that provides a lot of the
functional programming support that you would expect in
- Prototype.js
+ Prototype.js
(or Ruby),
but without extending any of the built-in JavaScript objects. It's the
tie to go along with jQuery's tux,
From fc37d55bbcd7912eff38e2eca8f912ea0c898729 Mon Sep 17 00:00:00 2001
From: Michael Ficarra
Date: Mon, 26 Nov 2012 03:20:38 -0800
Subject: [PATCH 18/40] fixes #868: _.times collects return values
---
test/utility.js | 4 +++-
underscore.js | 4 +++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/test/utility.js b/test/utility.js
index c9be20ad7..4fcc7d46a 100644
--- a/test/utility.js
+++ b/test/utility.js
@@ -37,8 +37,10 @@ $(document).ready(function() {
ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
//
vals = [];
- _(3).times(function (i) { vals.push(i); });
+ _(3).times(function(i) { vals.push(i); });
ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
+ // collects return values
+ ok(_.isEqual([0, 1, 2], _.times(3, function(i) { return i; })), "collects return values");
});
test("mixin", function() {
diff --git a/underscore.js b/underscore.js
index 3771cb422..95e231965 100644
--- a/underscore.js
+++ b/underscore.js
@@ -1000,7 +1000,9 @@
// Run a function **n** times.
_.times = function(n, iterator, context) {
- for (var i = 0; i < n; i++) iterator.call(context, i);
+ var accum = [];
+ for (var i = 0; i < n; i++) accum.push(iterator.call(context, i));
+ return accum;
};
// Return a random integer between min and max (inclusive).
From b1f266fa707e1c2467291fed4de3208b7228984d Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Mon, 26 Nov 2012 19:28:50 -0800
Subject: [PATCH 19/40] Optimize `_.times`.
---
underscore.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/underscore.js b/underscore.js
index 95e231965..957376a58 100644
--- a/underscore.js
+++ b/underscore.js
@@ -1000,8 +1000,8 @@
// Run a function **n** times.
_.times = function(n, iterator, context) {
- var accum = [];
- for (var i = 0; i < n; i++) accum.push(iterator.call(context, i));
+ var accum = Array(n);
+ for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
return accum;
};
From fb7814333b6c7ecfa08731d3a673f3ab0fc4012f Mon Sep 17 00:00:00 2001
From: Nick Fisher
Date: Sat, 1 Dec 2012 15:17:19 +0100
Subject: [PATCH 20/40] Use identity instead of creating a new function
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index 957376a58..d510a4a63 100644
--- a/underscore.js
+++ b/underscore.js
@@ -408,7 +408,7 @@
// Trim out all falsy values from an array.
_.compact = function(array) {
- return _.filter(array, function(value){ return !!value; });
+ return _.filter(array, _.identity);
};
// Internal implementation of a recursive `flatten` function.
From 6a6de5726eb2c03aa94c44513f86f6b07fa9cc3a Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sat, 1 Dec 2012 12:41:24 -0800
Subject: [PATCH 21/40] Ensure a bound instance is an instance of the bound and
original function.
---
test/functions.js | 4 +++-
underscore.js | 17 ++++++++++-------
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/test/functions.js b/test/functions.js
index a52965877..dc01f33ab 100644
--- a/test/functions.js
+++ b/test/functions.js
@@ -34,8 +34,10 @@ $(document).ready(function() {
// To test this with a modern browser, set underscore's nativeBind to undefined
var F = function () { return this; };
var Boundf = _.bind(F, {hello: "moe curly"});
- equal(new Boundf().hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
+ var newBoundf = new Boundf();
+ equal(newBoundf.hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
+ ok(newBoundf instanceof Boundf && newBoundf instanceof F, "a bound instance is an instance of the bound and original function");
});
test("bindAll", function() {
diff --git a/underscore.js b/underscore.js
index 957376a58..f40c60f9a 100644
--- a/underscore.js
+++ b/underscore.js
@@ -573,18 +573,21 @@
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
- var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
- args = slice.call(arguments, 2);
- return bound = function() {
+ var args = slice.call(arguments, 2);
+ var bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
- ctor.prototype = func.prototype;
- var self = new ctor;
- var result = func.apply(self, args.concat(slice.call(arguments)));
+ var result = func.apply(this, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
- return self;
+ return this;
};
+ if (func && func.prototype) {
+ ctor.prototype = func.prototype;
+ bound.prototype = new ctor;
+ ctor.prototype = null;
+ }
+ return bound;
};
// Bind all of an object's methods to that object. Useful for ensuring that
From a3e017509359994b1c6af35695a4ff9dc2662deb Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sat, 1 Dec 2012 13:02:37 -0800
Subject: [PATCH 22/40] Update `_.each` documentation. [closes #867]
---
index.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/index.html b/index.html
index 7010ee333..8940d2a79 100644
--- a/index.html
+++ b/index.html
@@ -406,10 +406,10 @@
Collection Functions (Arrays or Objects)
forEach function if it exists.
-_.each([1, 2, 3], function(num){ alert(num); });
+_.each([1, 2, 3], alert);
=> alerts each number in turn...
-_.each({one : 1, two : 2, three : 3}, function(num, key){ alert(num); });
-=> alerts each number in turn...
+_.each({one : 1, two : 2, three : 3}, alert);
+=> alerts each number value in turn...
map_.map(list, iterator, [context])
From 17e40e76933642f3749b9d124861b95653c83789 Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sat, 1 Dec 2012 13:17:57 -0800
Subject: [PATCH 23/40] Cleanup `_.toArray` unit test addition.
---
test/collections.js | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/test/collections.js b/test/collections.js
index 2cb3f6b67..2214ed79c 100644
--- a/test/collections.js
+++ b/test/collections.js
@@ -419,8 +419,12 @@ $(document).ready(function() {
var numbers = _.toArray({one : 1, two : 2, three : 3});
equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
- // _.toArray on a NodeList should not throw.
- ok(_.isArray(_.toArray(document.childNodes)));
+ // test in IE < 9
+ try {
+ var actual = _.toArray(document.childNodes);
+ } catch(ex) { }
+
+ ok(_.isArray(actual), 'should not throw converting a node list');
});
test('size', function() {
From 6aee64404ffa0dac039cae0a8a0f831fba0426f8 Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sat, 1 Dec 2012 17:15:19 -0800
Subject: [PATCH 24/40] Make Underscore work in Adobe's JS engine.
---
test/objects.js | 14 ++++++++++++++
underscore.js | 27 +++++++++++++++++++--------
2 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/test/objects.js b/test/objects.js
index ecb9ae4ac..31db69b10 100644
--- a/test/objects.js
+++ b/test/objects.js
@@ -53,6 +53,13 @@ $(document).ready(function() {
ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps');
result = _.extend({}, {a: void 0, b: null});
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
+
+ try {
+ result = {};
+ _.extend(result, null, undefined, { 'a': 1 });
+ } catch(ex) { }
+
+ equal(result.a, 1, 'should not error on `null` or `undefined` sources');
});
test("pick", function() {
@@ -96,6 +103,13 @@ $(document).ready(function() {
equal(options.empty, "", 'value exists');
ok(_.isNaN(options.nan), "NaN isn't overridden");
equal(options.word, "word", 'new value is added, first one wins');
+
+ try {
+ options = {};
+ _.defaults(options, null, undefined, { 'a': 1 });
+ } catch(ex) { }
+
+ equal(options.a, 1, 'should not error on `null` or `undefined` sources');
});
test("clone", function() {
diff --git a/underscore.js b/underscore.js
index dfe261029..73fc1a8e9 100644
--- a/underscore.js
+++ b/underscore.js
@@ -762,8 +762,10 @@
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
- for (var prop in source) {
- obj[prop] = source[prop];
+ if (source) {
+ for (var prop in source) {
+ obj[prop] = source[prop];
+ }
}
});
return obj;
@@ -792,8 +794,10 @@
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
- for (var prop in source) {
- if (obj[prop] == null) obj[prop] = source[prop];
+ if (source) {
+ for (var prop in source) {
+ if (obj[prop] == null) obj[prop] = source[prop];
+ }
}
});
return obj;
@@ -1121,11 +1125,18 @@
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
- source +=
- escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
- interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
- evaluate ? "';\n" + evaluate + "\n__p+='" : '';
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ }
+ if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ }
+ if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
index = offset + match.length;
+ return match;
});
source += "';\n";
From a7afc5607c69e3e13d3436036e34a48be8940d0d Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sun, 2 Dec 2012 18:38:31 -0800
Subject: [PATCH 25/40] Cleanup test/object.js to match existing coding style.
---
test/objects.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/test/objects.js b/test/objects.js
index 31db69b10..945b06339 100644
--- a/test/objects.js
+++ b/test/objects.js
@@ -56,8 +56,8 @@ $(document).ready(function() {
try {
result = {};
- _.extend(result, null, undefined, { 'a': 1 });
- } catch(ex) { }
+ _.extend(result, null, undefined, {a:1});
+ } catch(ex) {}
equal(result.a, 1, 'should not error on `null` or `undefined` sources');
});
@@ -106,8 +106,8 @@ $(document).ready(function() {
try {
options = {};
- _.defaults(options, null, undefined, { 'a': 1 });
- } catch(ex) { }
+ _.defaults(options, null, undefined, {a:1});
+ } catch(ex) {}
equal(options.a, 1, 'should not error on `null` or `undefined` sources');
});
From 3a4204f73db546ca3733b25774ba837e4df66750 Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sun, 2 Dec 2012 18:38:56 -0800
Subject: [PATCH 26/40] Make unit tests passable in IE < 8.
---
test/arrays.js | 12 +-
test/index.html | 1 +
test/vendor/json3.js | 783 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 789 insertions(+), 7 deletions(-)
create mode 100644 test/vendor/json3.js
diff --git a/test/arrays.js b/test/arrays.js
index f53074d67..a29822a1b 100644
--- a/test/arrays.js
+++ b/test/arrays.js
@@ -60,13 +60,11 @@ $(document).ready(function() {
});
test("flatten", function() {
- if (window.JSON) {
- var list = [1, [2], [3, [[[4]]]]];
- equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
- equal(JSON.stringify(_.flatten(list, true)), '[1,2,3,[[[4]]]]', 'can shallowly flatten nested arrays');
- var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]);
- equal(JSON.stringify(result), '[1,2,3,4]', 'works on an arguments object');
- }
+ var list = [1, [2], [3, [[[4]]]]];
+ equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
+ equal(JSON.stringify(_.flatten(list, true)), '[1,2,3,[[[4]]]]', 'can shallowly flatten nested arrays');
+ var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]);
+ equal(JSON.stringify(result), '[1,2,3,4]', 'works on an arguments object');
});
test("without", function() {
diff --git a/test/index.html b/test/index.html
index ea7a13603..196714e34 100644
--- a/test/index.html
+++ b/test/index.html
@@ -3,6 +3,7 @@
Underscore Test Suite
+
diff --git a/test/vendor/json3.js b/test/vendor/json3.js
new file mode 100644
index 000000000..b152b27ff
--- /dev/null
+++ b/test/vendor/json3.js
@@ -0,0 +1,783 @@
+/*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */
+;(function () {
+ // Convenience aliases.
+ var getClass = {}.toString, isProperty, forEach, undef;
+
+ // Detect the `define` function exposed by asynchronous module loaders. The
+ // strict `define` check is necessary for compatibility with `r.js`.
+ var isLoader = typeof define === "function" && define.amd, JSON3 = !isLoader && typeof exports == "object" && exports;
+
+ if (JSON3 || isLoader) {
+ if (typeof JSON == "object" && JSON) {
+ // Delegate to the native `stringify` and `parse` implementations in
+ // asynchronous module loaders and CommonJS environments.
+ if (isLoader) {
+ JSON3 = JSON;
+ } else {
+ JSON3.stringify = JSON.stringify;
+ JSON3.parse = JSON.parse;
+ }
+ } else if (isLoader) {
+ JSON3 = this.JSON = {};
+ }
+ } else {
+ // Export for web browsers and JavaScript engines.
+ JSON3 = this.JSON || (this.JSON = {});
+ }
+
+ // Local variables.
+ var Escapes, toPaddedString, quote, serialize;
+ var fromCharCode, Unescapes, abort, lex, get, walk, update, Index, Source;
+
+ // Test the `Date#getUTC*` methods. Based on work by @Yaffle.
+ var isExtended = new Date(-3509827334573292), floor, Months, getDay;
+
+ try {
+ // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
+ // results for certain dates in Opera >= 10.53.
+ isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() == 1 &&
+ // Safari < 2.0.2 stores the internal millisecond time value correctly,
+ // but clips the values returned by the date methods to the range of
+ // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
+ isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
+ } catch (exception) {}
+
+ // Internal: Determines whether the native `JSON.stringify` and `parse`
+ // implementations are spec-compliant. Based on work by Ken Snyder.
+ function has(name) {
+ var stringifySupported, parseSupported, value, serialized = '{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', all = name == "json";
+ if (all || name == "json-stringify" || name == "json-parse") {
+ // Test `JSON.stringify`.
+ if (name == "json-stringify" || all) {
+ if ((stringifySupported = typeof JSON3.stringify == "function" && isExtended)) {
+ // A test function object with a custom `toJSON` method.
+ (value = function () {
+ return 1;
+ }).toJSON = value;
+ try {
+ stringifySupported =
+ // Firefox 3.1b1 and b2 serialize string, number, and boolean
+ // primitives as object literals.
+ JSON3.stringify(0) === "0" &&
+ // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
+ // literals.
+ JSON3.stringify(new Number()) === "0" &&
+ JSON3.stringify(new String()) == '""' &&
+ // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
+ // does not define a canonical JSON representation (this applies to
+ // objects with `toJSON` properties as well, *unless* they are nested
+ // within an object or array).
+ JSON3.stringify(getClass) === undef &&
+ // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
+ // FF 3.1b3 pass this test.
+ JSON3.stringify(undef) === undef &&
+ // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
+ // respectively, if the value is omitted entirely.
+ JSON3.stringify() === undef &&
+ // FF 3.1b1, 2 throw an error if the given value is not a number,
+ // string, array, object, Boolean, or `null` literal. This applies to
+ // objects with custom `toJSON` methods as well, unless they are nested
+ // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
+ // methods entirely.
+ JSON3.stringify(value) === "1" &&
+ JSON3.stringify([value]) == "[1]" &&
+ // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
+ // `"[null]"`.
+ JSON3.stringify([undef]) == "[null]" &&
+ // YUI 3.0.0b1 fails to serialize `null` literals.
+ JSON3.stringify(null) == "null" &&
+ // FF 3.1b1, 2 halts serialization if an array contains a function:
+ // `[1, true, getClass, 1]` serializes as "[1,true,],". These versions
+ // of Firefox also allow trailing commas in JSON objects and arrays.
+ // FF 3.1b3 elides non-JSON values from objects and arrays, unless they
+ // define custom `toJSON` methods.
+ JSON3.stringify([undef, getClass, null]) == "[null,null,null]" &&
+ // Simple serialization test. FF 3.1b1 uses Unicode escape sequences
+ // where character escape codes are expected (e.g., `\b` => `\u0008`).
+ JSON3.stringify({ "A": [value, true, false, null, "\0\b\n\f\r\t"] }) == serialized &&
+ // FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
+ JSON3.stringify(null, value) === "1" &&
+ JSON3.stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
+ // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
+ // serialize extended years.
+ JSON3.stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
+ // The milliseconds are optional in ES 5, but required in 5.1.
+ JSON3.stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
+ // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
+ // four-digit years instead of six-digit years. Credits: @Yaffle.
+ JSON3.stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
+ // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
+ // values less than 1000. Credits: @Yaffle.
+ JSON3.stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
+ } catch (exception) {
+ stringifySupported = false;
+ }
+ }
+ if (!all) {
+ return stringifySupported;
+ }
+ }
+ // Test `JSON.parse`.
+ if (name == "json-parse" || all) {
+ if (typeof JSON3.parse == "function") {
+ try {
+ // FF 3.1b1, b2 will throw an exception if a bare literal is provided.
+ // Conforming implementations should also coerce the initial argument to
+ // a string prior to parsing.
+ if (JSON3.parse("0") === 0 && !JSON3.parse(false)) {
+ // Simple parsing test.
+ value = JSON3.parse(serialized);
+ if ((parseSupported = value.A.length == 5 && value.A[0] == 1)) {
+ try {
+ // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
+ parseSupported = !JSON3.parse('"\t"');
+ } catch (exception) {}
+ if (parseSupported) {
+ try {
+ // FF 4.0 and 4.0.1 allow leading `+` signs, and leading and
+ // trailing decimal points. FF 4.0, 4.0.1, and IE 9-10 also
+ // allow certain octal literals.
+ parseSupported = JSON3.parse("01") != 1;
+ } catch (exception) {}
+ }
+ }
+ }
+ } catch (exception) {
+ parseSupported = false;
+ }
+ }
+ if (!all) {
+ return parseSupported;
+ }
+ }
+ return stringifySupported && parseSupported;
+ }
+ }
+
+ if (!has("json")) {
+ // Define additional utility methods if the `Date` methods are buggy.
+ if (!isExtended) {
+ floor = Math.floor;
+ // A mapping between the months of the year and the number of days between
+ // January 1st and the first of the respective month.
+ Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
+ // Internal: Calculates the number of days between the Unix epoch and the
+ // first day of the given month.
+ getDay = function (year, month) {
+ return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
+ };
+ }
+
+ // Internal: Determines if a property is a direct property of the given
+ // object. Delegates to the native `Object#hasOwnProperty` method.
+ if (!(isProperty = {}.hasOwnProperty)) {
+ isProperty = function (property) {
+ var members = {}, constructor;
+ if ((members.__proto__ = null, members.__proto__ = {
+ // The *proto* property cannot be set multiple times in recent
+ // versions of Firefox and SeaMonkey.
+ "toString": 1
+ }, members).toString != getClass) {
+ // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
+ // supports the mutable *proto* property.
+ isProperty = function (property) {
+ // Capture and break the object's prototype chain (see section 8.6.2
+ // of the ES 5.1 spec). The parenthesized expression prevents an
+ // unsafe transformation by the Closure Compiler.
+ var original = this.__proto__, result = property in (this.__proto__ = null, this);
+ // Restore the original prototype chain.
+ this.__proto__ = original;
+ return result;
+ };
+ } else {
+ // Capture a reference to the top-level `Object` constructor.
+ constructor = members.constructor;
+ // Use the `constructor` property to simulate `Object#hasOwnProperty` in
+ // other environments.
+ isProperty = function (property) {
+ var parent = (this.constructor || constructor).prototype;
+ return property in this && !(property in parent && this[property] === parent[property]);
+ };
+ }
+ members = null;
+ return isProperty.call(this, property);
+ };
+ }
+
+ // Internal: Normalizes the `for...in` iteration algorithm across
+ // environments. Each enumerated key is yielded to a `callback` function.
+ forEach = function (object, callback) {
+ var size = 0, Properties, members, property, forEach;
+
+ // Tests for bugs in the current environment's `for...in` algorithm. The
+ // `valueOf` property inherits the non-enumerable flag from
+ // `Object.prototype` in older versions of IE, Netscape, and Mozilla.
+ (Properties = function () {
+ this.valueOf = 0;
+ }).prototype.valueOf = 0;
+
+ // Iterate over a new instance of the `Properties` class.
+ members = new Properties();
+ for (property in members) {
+ // Ignore all properties inherited from `Object.prototype`.
+ if (isProperty.call(members, property)) {
+ size++;
+ }
+ }
+ Properties = members = null;
+
+ // Normalize the iteration algorithm.
+ if (!size) {
+ // A list of non-enumerable properties inherited from `Object.prototype`.
+ members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
+ // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
+ // properties.
+ forEach = function (object, callback) {
+ var isFunction = getClass.call(object) == "[object Function]", property, length;
+ for (property in object) {
+ // Gecko <= 1.0 enumerates the `prototype` property of functions under
+ // certain conditions; IE does not.
+ if (!(isFunction && property == "prototype") && isProperty.call(object, property)) {
+ callback(property);
+ }
+ }
+ // Manually invoke the callback for each non-enumerable property.
+ for (length = members.length; property = members[--length]; isProperty.call(object, property) && callback(property));
+ };
+ } else if (size == 2) {
+ // Safari <= 2.0.4 enumerates shadowed properties twice.
+ forEach = function (object, callback) {
+ // Create a set of iterated properties.
+ var members = {}, isFunction = getClass.call(object) == "[object Function]", property;
+ for (property in object) {
+ // Store each property name to prevent double enumeration. The
+ // `prototype` property of functions is not enumerated due to cross-
+ // environment inconsistencies.
+ if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
+ callback(property);
+ }
+ }
+ };
+ } else {
+ // No bugs detected; use the standard `for...in` algorithm.
+ forEach = function (object, callback) {
+ var isFunction = getClass.call(object) == "[object Function]", property, isConstructor;
+ for (property in object) {
+ if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
+ callback(property);
+ }
+ }
+ // Manually invoke the callback for the `constructor` property due to
+ // cross-environment inconsistencies.
+ if (isConstructor || isProperty.call(object, (property = "constructor"))) {
+ callback(property);
+ }
+ };
+ }
+ return forEach(object, callback);
+ };
+
+ // Public: Serializes a JavaScript `value` as a JSON string. The optional
+ // `filter` argument may specify either a function that alters how object and
+ // array members are serialized, or an array of strings and numbers that
+ // indicates which properties should be serialized. The optional `width`
+ // argument may be either a string or number that specifies the indentation
+ // level of the output.
+ if (!has("json-stringify")) {
+ // Internal: A map of control characters and their escaped equivalents.
+ Escapes = {
+ "\\": "\\\\",
+ '"': '\\"',
+ "\b": "\\b",
+ "\f": "\\f",
+ "\n": "\\n",
+ "\r": "\\r",
+ "\t": "\\t"
+ };
+
+ // Internal: Converts `value` into a zero-padded string such that its
+ // length is at least equal to `width`. The `width` must be <= 6.
+ toPaddedString = function (width, value) {
+ // The `|| 0` expression is necessary to work around a bug in
+ // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
+ return ("000000" + (value || 0)).slice(-width);
+ };
+
+ // Internal: Double-quotes a string `value`, replacing all ASCII control
+ // characters (characters with code unit values between 0 and 31) with
+ // their escaped equivalents. This is an implementation of the
+ // `Quote(value)` operation defined in ES 5.1 section 15.12.3.
+ quote = function (value) {
+ var result = '"', index = 0, symbol;
+ for (; symbol = value.charAt(index); index++) {
+ // Escape the reverse solidus, double quote, backspace, form feed, line
+ // feed, carriage return, and tab characters.
+ result += '\\"\b\f\n\r\t'.indexOf(symbol) > -1 ? Escapes[symbol] :
+ // If the character is a control character, append its Unicode escape
+ // sequence; otherwise, append the character as-is.
+ (Escapes[symbol] = symbol < " " ? "\\u00" + toPaddedString(2, symbol.charCodeAt(0).toString(16)) : symbol);
+ }
+ return result + '"';
+ };
+
+ // Internal: Recursively serializes an object. Implements the
+ // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
+ serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
+ var value = object[property], className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, any, result;
+ if (typeof value == "object" && value) {
+ className = getClass.call(value);
+ if (className == "[object Date]" && !isProperty.call(value, "toJSON")) {
+ if (value > -1 / 0 && value < 1 / 0) {
+ // Dates are serialized according to the `Date#toJSON` method
+ // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
+ // for the ISO 8601 date time string format.
+ if (getDay) {
+ // Manually compute the year, month, date, hours, minutes,
+ // seconds, and milliseconds if the `getUTC*` methods are
+ // buggy. Adapted from @Yaffle's `date-shim` project.
+ date = floor(value / 864e5);
+ for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
+ for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
+ date = 1 + date - getDay(year, month);
+ // The `time` value specifies the time within the day (see ES
+ // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
+ // to compute `A modulo B`, as the `%` operator does not
+ // correspond to the `modulo` operation for negative numbers.
+ time = (value % 864e5 + 864e5) % 864e5;
+ // The hours, minutes, seconds, and milliseconds are obtained by
+ // decomposing the time within the day. See section 15.9.1.10.
+ hours = floor(time / 36e5) % 24;
+ minutes = floor(time / 6e4) % 60;
+ seconds = floor(time / 1e3) % 60;
+ milliseconds = time % 1e3;
+ } else {
+ year = value.getUTCFullYear();
+ month = value.getUTCMonth();
+ date = value.getUTCDate();
+ hours = value.getUTCHours();
+ minutes = value.getUTCMinutes();
+ seconds = value.getUTCSeconds();
+ milliseconds = value.getUTCMilliseconds();
+ }
+ // Serialize extended years correctly.
+ value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
+ "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
+ // Months, dates, hours, minutes, and seconds should have two
+ // digits; milliseconds should have three.
+ "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
+ // Milliseconds are optional in ES 5.0, but required in 5.1.
+ "." + toPaddedString(3, milliseconds) + "Z";
+ } else {
+ value = null;
+ }
+ } else if (typeof value.toJSON == "function" && ((className != "[object Number]" && className != "[object String]" && className != "[object Array]") || isProperty.call(value, "toJSON"))) {
+ // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
+ // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
+ // ignores all `toJSON` methods on these objects unless they are
+ // defined directly on an instance.
+ value = value.toJSON(property);
+ }
+ }
+ if (callback) {
+ // If a replacement function was provided, call it to obtain the value
+ // for serialization.
+ value = callback.call(object, property, value);
+ }
+ if (value === null) {
+ return "null";
+ }
+ className = getClass.call(value);
+ if (className == "[object Boolean]") {
+ // Booleans are represented literally.
+ return "" + value;
+ } else if (className == "[object Number]") {
+ // JSON numbers must be finite. `Infinity` and `NaN` are serialized as
+ // `"null"`.
+ return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
+ } else if (className == "[object String]") {
+ // Strings are double-quoted and escaped.
+ return quote(value);
+ }
+ // Recursively serialize objects and arrays.
+ if (typeof value == "object") {
+ // Check for cyclic structures. This is a linear search; performance
+ // is inversely proportional to the number of unique nested objects.
+ for (length = stack.length; length--;) {
+ if (stack[length] === value) {
+ // Cyclic structures cannot be serialized by `JSON.stringify`.
+ throw TypeError();
+ }
+ }
+ // Add the object to the stack of traversed objects.
+ stack.push(value);
+ results = [];
+ // Save the current indentation level and indent one additional level.
+ prefix = indentation;
+ indentation += whitespace;
+ if (className == "[object Array]") {
+ // Recursively serialize array elements.
+ for (index = 0, length = value.length; index < length; any || (any = true), index++) {
+ element = serialize(index, value, callback, properties, whitespace, indentation, stack);
+ results.push(element === undef ? "null" : element);
+ }
+ result = any ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
+ } else {
+ // Recursively serialize object members. Members are selected from
+ // either a user-specified list of property names, or the object
+ // itself.
+ forEach(properties || value, function (property) {
+ var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
+ if (element !== undef) {
+ // According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
+ // is not the empty string, let `member` {quote(property) + ":"}
+ // be the concatenation of `member` and the `space` character."
+ // The "`space` character" refers to the literal space
+ // character, not the `space` {width} argument provided to
+ // `JSON.stringify`.
+ results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
+ }
+ any || (any = true);
+ });
+ result = any ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
+ }
+ // Remove the object from the traversed object stack.
+ stack.pop();
+ return result;
+ }
+ };
+
+ // Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
+ JSON3.stringify = function (source, filter, width) {
+ var whitespace, callback, properties, index, length, value;
+ if (typeof filter == "function" || typeof filter == "object" && filter) {
+ if (getClass.call(filter) == "[object Function]") {
+ callback = filter;
+ } else if (getClass.call(filter) == "[object Array]") {
+ // Convert the property names array into a makeshift set.
+ properties = {};
+ for (index = 0, length = filter.length; index < length; value = filter[index++], ((getClass.call(value) == "[object String]" || getClass.call(value) == "[object Number]") && (properties[value] = 1)));
+ }
+ }
+ if (width) {
+ if (getClass.call(width) == "[object Number]") {
+ // Convert the `width` to an integer and create a string containing
+ // `width` number of space characters.
+ if ((width -= width % 1) > 0) {
+ for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
+ }
+ } else if (getClass.call(width) == "[object String]") {
+ whitespace = width.length <= 10 ? width : width.slice(0, 10);
+ }
+ }
+ // Opera <= 7.54u2 discards the values associated with empty string keys
+ // (`""`) only if they are used directly within an object member list
+ // (e.g., `!("" in { "": 1})`).
+ return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
+ };
+ }
+
+ // Public: Parses a JSON source string.
+ if (!has("json-parse")) {
+ fromCharCode = String.fromCharCode;
+ // Internal: A map of escaped control characters and their unescaped
+ // equivalents.
+ Unescapes = {
+ "\\": "\\",
+ '"': '"',
+ "/": "/",
+ "b": "\b",
+ "t": "\t",
+ "n": "\n",
+ "f": "\f",
+ "r": "\r"
+ };
+
+ // Internal: Resets the parser state and throws a `SyntaxError`.
+ abort = function() {
+ Index = Source = null;
+ throw SyntaxError();
+ };
+
+ // Internal: Returns the next token, or `"$"` if the parser has reached
+ // the end of the source string. A token may be a string, number, `null`
+ // literal, or Boolean literal.
+ lex = function () {
+ var source = Source, length = source.length, symbol, value, begin, position, sign;
+ while (Index < length) {
+ symbol = source.charAt(Index);
+ if ("\t\r\n ".indexOf(symbol) > -1) {
+ // Skip whitespace tokens, including tabs, carriage returns, line
+ // feeds, and space characters.
+ Index++;
+ } else if ("{}[]:,".indexOf(symbol) > -1) {
+ // Parse a punctuator token at the current position.
+ Index++;
+ return symbol;
+ } else if (symbol == '"') {
+ // Advance to the next character and parse a JSON string at the
+ // current position. String tokens are prefixed with the sentinel
+ // `@` character to distinguish them from punctuators.
+ for (value = "@", Index++; Index < length;) {
+ symbol = source.charAt(Index);
+ if (symbol < " ") {
+ // Unescaped ASCII control characters are not permitted.
+ abort();
+ } else if (symbol == "\\") {
+ // Parse escaped JSON control characters, `"`, `\`, `/`, and
+ // Unicode escape sequences.
+ symbol = source.charAt(++Index);
+ if ('\\"/btnfr'.indexOf(symbol) > -1) {
+ // Revive escaped control characters.
+ value += Unescapes[symbol];
+ Index++;
+ } else if (symbol == "u") {
+ // Advance to the first character of the escape sequence.
+ begin = ++Index;
+ // Validate the Unicode escape sequence.
+ for (position = Index + 4; Index < position; Index++) {
+ symbol = source.charAt(Index);
+ // A valid sequence comprises four hexdigits that form a
+ // single hexadecimal value.
+ if (!(symbol >= "0" && symbol <= "9" || symbol >= "a" && symbol <= "f" || symbol >= "A" && symbol <= "F")) {
+ // Invalid Unicode escape sequence.
+ abort();
+ }
+ }
+ // Revive the escaped character.
+ value += fromCharCode("0x" + source.slice(begin, Index));
+ } else {
+ // Invalid escape sequence.
+ abort();
+ }
+ } else {
+ if (symbol == '"') {
+ // An unescaped double-quote character marks the end of the
+ // string.
+ break;
+ }
+ // Append the original character as-is.
+ value += symbol;
+ Index++;
+ }
+ }
+ if (source.charAt(Index) == '"') {
+ Index++;
+ // Return the revived string.
+ return value;
+ }
+ // Unterminated string.
+ abort();
+ } else {
+ // Parse numbers and literals.
+ begin = Index;
+ // Advance the scanner's position past the sign, if one is
+ // specified.
+ if (symbol == "-") {
+ sign = true;
+ symbol = source.charAt(++Index);
+ }
+ // Parse an integer or floating-point value.
+ if (symbol >= "0" && symbol <= "9") {
+ // Leading zeroes are interpreted as octal literals.
+ if (symbol == "0" && (symbol = source.charAt(Index + 1), symbol >= "0" && symbol <= "9")) {
+ // Illegal octal literal.
+ abort();
+ }
+ sign = false;
+ // Parse the integer component.
+ for (; Index < length && (symbol = source.charAt(Index), symbol >= "0" && symbol <= "9"); Index++);
+ // Floats cannot contain a leading decimal point; however, this
+ // case is already accounted for by the parser.
+ if (source.charAt(Index) == ".") {
+ position = ++Index;
+ // Parse the decimal component.
+ for (; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
+ if (position == Index) {
+ // Illegal trailing decimal.
+ abort();
+ }
+ Index = position;
+ }
+ // Parse exponents.
+ symbol = source.charAt(Index);
+ if (symbol == "e" || symbol == "E") {
+ // Skip past the sign following the exponent, if one is
+ // specified.
+ symbol = source.charAt(++Index);
+ if (symbol == "+" || symbol == "-") {
+ Index++;
+ }
+ // Parse the exponential component.
+ for (position = Index; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
+ if (position == Index) {
+ // Illegal empty exponent.
+ abort();
+ }
+ Index = position;
+ }
+ // Coerce the parsed value to a JavaScript number.
+ return +source.slice(begin, Index);
+ }
+ // A negative sign may only precede numbers.
+ if (sign) {
+ abort();
+ }
+ // `true`, `false`, and `null` literals.
+ if (source.slice(Index, Index + 4) == "true") {
+ Index += 4;
+ return true;
+ } else if (source.slice(Index, Index + 5) == "false") {
+ Index += 5;
+ return false;
+ } else if (source.slice(Index, Index + 4) == "null") {
+ Index += 4;
+ return null;
+ }
+ // Unrecognized token.
+ abort();
+ }
+ }
+ // Return the sentinel `$` character if the parser has reached the end
+ // of the source string.
+ return "$";
+ };
+
+ // Internal: Parses a JSON `value` token.
+ get = function (value) {
+ var results, any, key;
+ if (value == "$") {
+ // Unexpected end of input.
+ abort();
+ }
+ if (typeof value == "string") {
+ if (value.charAt(0) == "@") {
+ // Remove the sentinel `@` character.
+ return value.slice(1);
+ }
+ // Parse object and array literals.
+ if (value == "[") {
+ // Parses a JSON array, returning a new JavaScript array.
+ results = [];
+ for (;; any || (any = true)) {
+ value = lex();
+ // A closing square bracket marks the end of the array literal.
+ if (value == "]") {
+ break;
+ }
+ // If the array literal contains elements, the current token
+ // should be a comma separating the previous element from the
+ // next.
+ if (any) {
+ if (value == ",") {
+ value = lex();
+ if (value == "]") {
+ // Unexpected trailing `,` in array literal.
+ abort();
+ }
+ } else {
+ // A `,` must separate each array element.
+ abort();
+ }
+ }
+ // Elisions and leading commas are not permitted.
+ if (value == ",") {
+ abort();
+ }
+ results.push(get(value));
+ }
+ return results;
+ } else if (value == "{") {
+ // Parses a JSON object, returning a new JavaScript object.
+ results = {};
+ for (;; any || (any = true)) {
+ value = lex();
+ // A closing curly brace marks the end of the object literal.
+ if (value == "}") {
+ break;
+ }
+ // If the object literal contains members, the current token
+ // should be a comma separator.
+ if (any) {
+ if (value == ",") {
+ value = lex();
+ if (value == "}") {
+ // Unexpected trailing `,` in object literal.
+ abort();
+ }
+ } else {
+ // A `,` must separate each object member.
+ abort();
+ }
+ }
+ // Leading commas are not permitted, object property names must be
+ // double-quoted strings, and a `:` must separate each property
+ // name and value.
+ if (value == "," || typeof value != "string" || value.charAt(0) != "@" || lex() != ":") {
+ abort();
+ }
+ results[value.slice(1)] = get(lex());
+ }
+ return results;
+ }
+ // Unexpected token encountered.
+ abort();
+ }
+ return value;
+ };
+
+ // Internal: Updates a traversed object member.
+ update = function(source, property, callback) {
+ var element = walk(source, property, callback);
+ if (element === undef) {
+ delete source[property];
+ } else {
+ source[property] = element;
+ }
+ };
+
+ // Internal: Recursively traverses a parsed JSON object, invoking the
+ // `callback` function for each value. This is an implementation of the
+ // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
+ walk = function (source, property, callback) {
+ var value = source[property], length;
+ if (typeof value == "object" && value) {
+ if (getClass.call(value) == "[object Array]") {
+ for (length = value.length; length--;) {
+ update(value, length, callback);
+ }
+ } else {
+ // `forEach` can't be used to traverse an array in Opera <= 8.54,
+ // as `Object#hasOwnProperty` returns `false` for array indices
+ // (e.g., `![1, 2, 3].hasOwnProperty("0")`).
+ forEach(value, function (property) {
+ update(value, property, callback);
+ });
+ }
+ }
+ return callback.call(source, property, value);
+ };
+
+ // Public: `JSON.parse`. See ES 5.1 section 15.12.2.
+ JSON3.parse = function (source, callback) {
+ var result, value;
+ Index = 0;
+ Source = source;
+ result = get(lex());
+ // If a JSON string contains multiple tokens, it is invalid.
+ if (lex() != "$") {
+ abort();
+ }
+ // Reset the parser state.
+ Index = Source = null;
+ return callback && getClass.call(callback) == "[object Function]" ? walk((value = {}, value[""] = result, value), "", callback) : result;
+ };
+ }
+ }
+
+ // Export for asynchronous module loaders.
+ if (isLoader) {
+ define(function () {
+ return JSON3;
+ });
+ }
+}).call(this);
\ No newline at end of file
From b9872ea7f5187ddd8ec0d3f9739e741f77708e81 Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sun, 2 Dec 2012 21:12:17 -0800
Subject: [PATCH 27/40] Avoid OOP wrapper unless explicitly testing it in
test/arrays.js.
---
test/arrays.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/arrays.js b/test/arrays.js
index a29822a1b..5ad8b651d 100644
--- a/test/arrays.js
+++ b/test/arrays.js
@@ -55,7 +55,7 @@ $(document).ready(function() {
test("compact", function() {
equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
- var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3);
+ var result = (function(){ return _.compact(arguments).length; })(0, 1, false, 2, false, 3);
equal(result, 3, 'works on an arguments object');
});
From af580598b35add402d0263d656cf7d2b71a3e5fa Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sun, 2 Dec 2012 21:12:56 -0800
Subject: [PATCH 28/40] Remove no longer relevant custom `isEqual` method tests
in test/objects.js.
---
test/objects.js | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/test/objects.js b/test/objects.js
index 945b06339..932f48919 100644
--- a/test/objects.js
+++ b/test/objects.js
@@ -351,16 +351,10 @@ $(document).ready(function() {
// Chaining.
ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal');
- equal(_({x: 1, y: 2}).chain().isEqual(_({x: 1, y: 2}).chain()).value(), true, '`isEqual` can be chained');
- // Custom `isEqual` methods.
- var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}};
- var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}};
-
- ok(_.isEqual(isEqualObj, isEqualObjClone), 'Both objects implement identical `isEqual` methods');
- ok(_.isEqual(isEqualObjClone, isEqualObj), 'Commutative equality is implemented for objects with custom `isEqual` methods');
- ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal');
- ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods');
+ a = _({x: 1, y: 2}).chain();
+ b = _({x: 1, y: 2}).chain();
+ equal(_.isEqual(a.isEqual(b), _(true)), true, '`isEqual` can be chained');
// Objects from another frame.
ok(_.isEqual({}, iObject));
From 9179313c232b781c16786eb67f8a5942535bab9d Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Sun, 2 Dec 2012 21:15:18 -0800
Subject: [PATCH 29/40] Cleanup `_.template` test in test/utility.js
---
test/utility.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/test/utility.js b/test/utility.js
index 4fcc7d46a..4da269ddb 100644
--- a/test/utility.js
+++ b/test/utility.js
@@ -175,10 +175,11 @@ $(document).ready(function() {
test('_.template provides the generated function source, when a SyntaxError occurs', function() {
try {
- _.template('<%= if %>');
- } catch (e) {
- ok(e.source.indexOf('( if )') > 0);
+ _.template('<%= if x %>');
+ } catch (ex) {
+ var source = ex.source;
}
+ ok(/__p/.test(source));
});
test('_.template handles \\u2028 & \\u2029', function() {
From 076adcac2585adcc29a9b40468495971b43b8fce Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Mon, 3 Dec 2012 10:52:48 -0500
Subject: [PATCH 30/40] Fixes #877 -- uniqueId treats prefixes as strings and
returns strings
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index 73fc1a8e9..c91e50557 100644
--- a/underscore.js
+++ b/underscore.js
@@ -1076,7 +1076,7 @@
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
- return prefix ? prefix + id : id;
+ return '' + (prefix ? '' + prefix + id : id);
};
// By default, Underscore uses ERB-style template delimiters, change the
From 05585c64050068eebbde203c268cb52f336f2c5c Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Mon, 3 Dec 2012 11:31:03 -0500
Subject: [PATCH 31/40] removing json3 dependency in favor of qunit's deepEqual
---
test/arrays.js | 6 +-
test/index.html | 1 -
test/vendor/json3.js | 783 -------------------------------------------
3 files changed, 3 insertions(+), 787 deletions(-)
delete mode 100644 test/vendor/json3.js
diff --git a/test/arrays.js b/test/arrays.js
index 5ad8b651d..7145099b4 100644
--- a/test/arrays.js
+++ b/test/arrays.js
@@ -61,10 +61,10 @@ $(document).ready(function() {
test("flatten", function() {
var list = [1, [2], [3, [[[4]]]]];
- equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
- equal(JSON.stringify(_.flatten(list, true)), '[1,2,3,[[[4]]]]', 'can shallowly flatten nested arrays');
+ deepEqual(_.flatten(list), [1,2,3,4], 'can flatten nested arrays');
+ deepEqual(_.flatten(list, true), [1,2,3,[[[4]]]], 'can shallowly flatten nested arrays');
var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]);
- equal(JSON.stringify(result), '[1,2,3,4]', 'works on an arguments object');
+ deepEqual(result, [1,2,3,4], 'works on an arguments object');
});
test("without", function() {
diff --git a/test/index.html b/test/index.html
index 196714e34..ea7a13603 100644
--- a/test/index.html
+++ b/test/index.html
@@ -3,7 +3,6 @@
Underscore Test Suite
-
diff --git a/test/vendor/json3.js b/test/vendor/json3.js
deleted file mode 100644
index b152b27ff..000000000
--- a/test/vendor/json3.js
+++ /dev/null
@@ -1,783 +0,0 @@
-/*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */
-;(function () {
- // Convenience aliases.
- var getClass = {}.toString, isProperty, forEach, undef;
-
- // Detect the `define` function exposed by asynchronous module loaders. The
- // strict `define` check is necessary for compatibility with `r.js`.
- var isLoader = typeof define === "function" && define.amd, JSON3 = !isLoader && typeof exports == "object" && exports;
-
- if (JSON3 || isLoader) {
- if (typeof JSON == "object" && JSON) {
- // Delegate to the native `stringify` and `parse` implementations in
- // asynchronous module loaders and CommonJS environments.
- if (isLoader) {
- JSON3 = JSON;
- } else {
- JSON3.stringify = JSON.stringify;
- JSON3.parse = JSON.parse;
- }
- } else if (isLoader) {
- JSON3 = this.JSON = {};
- }
- } else {
- // Export for web browsers and JavaScript engines.
- JSON3 = this.JSON || (this.JSON = {});
- }
-
- // Local variables.
- var Escapes, toPaddedString, quote, serialize;
- var fromCharCode, Unescapes, abort, lex, get, walk, update, Index, Source;
-
- // Test the `Date#getUTC*` methods. Based on work by @Yaffle.
- var isExtended = new Date(-3509827334573292), floor, Months, getDay;
-
- try {
- // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
- // results for certain dates in Opera >= 10.53.
- isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() == 1 &&
- // Safari < 2.0.2 stores the internal millisecond time value correctly,
- // but clips the values returned by the date methods to the range of
- // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
- isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
- } catch (exception) {}
-
- // Internal: Determines whether the native `JSON.stringify` and `parse`
- // implementations are spec-compliant. Based on work by Ken Snyder.
- function has(name) {
- var stringifySupported, parseSupported, value, serialized = '{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', all = name == "json";
- if (all || name == "json-stringify" || name == "json-parse") {
- // Test `JSON.stringify`.
- if (name == "json-stringify" || all) {
- if ((stringifySupported = typeof JSON3.stringify == "function" && isExtended)) {
- // A test function object with a custom `toJSON` method.
- (value = function () {
- return 1;
- }).toJSON = value;
- try {
- stringifySupported =
- // Firefox 3.1b1 and b2 serialize string, number, and boolean
- // primitives as object literals.
- JSON3.stringify(0) === "0" &&
- // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
- // literals.
- JSON3.stringify(new Number()) === "0" &&
- JSON3.stringify(new String()) == '""' &&
- // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
- // does not define a canonical JSON representation (this applies to
- // objects with `toJSON` properties as well, *unless* they are nested
- // within an object or array).
- JSON3.stringify(getClass) === undef &&
- // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
- // FF 3.1b3 pass this test.
- JSON3.stringify(undef) === undef &&
- // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
- // respectively, if the value is omitted entirely.
- JSON3.stringify() === undef &&
- // FF 3.1b1, 2 throw an error if the given value is not a number,
- // string, array, object, Boolean, or `null` literal. This applies to
- // objects with custom `toJSON` methods as well, unless they are nested
- // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
- // methods entirely.
- JSON3.stringify(value) === "1" &&
- JSON3.stringify([value]) == "[1]" &&
- // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
- // `"[null]"`.
- JSON3.stringify([undef]) == "[null]" &&
- // YUI 3.0.0b1 fails to serialize `null` literals.
- JSON3.stringify(null) == "null" &&
- // FF 3.1b1, 2 halts serialization if an array contains a function:
- // `[1, true, getClass, 1]` serializes as "[1,true,],". These versions
- // of Firefox also allow trailing commas in JSON objects and arrays.
- // FF 3.1b3 elides non-JSON values from objects and arrays, unless they
- // define custom `toJSON` methods.
- JSON3.stringify([undef, getClass, null]) == "[null,null,null]" &&
- // Simple serialization test. FF 3.1b1 uses Unicode escape sequences
- // where character escape codes are expected (e.g., `\b` => `\u0008`).
- JSON3.stringify({ "A": [value, true, false, null, "\0\b\n\f\r\t"] }) == serialized &&
- // FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
- JSON3.stringify(null, value) === "1" &&
- JSON3.stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
- // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
- // serialize extended years.
- JSON3.stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
- // The milliseconds are optional in ES 5, but required in 5.1.
- JSON3.stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
- // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
- // four-digit years instead of six-digit years. Credits: @Yaffle.
- JSON3.stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
- // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
- // values less than 1000. Credits: @Yaffle.
- JSON3.stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
- } catch (exception) {
- stringifySupported = false;
- }
- }
- if (!all) {
- return stringifySupported;
- }
- }
- // Test `JSON.parse`.
- if (name == "json-parse" || all) {
- if (typeof JSON3.parse == "function") {
- try {
- // FF 3.1b1, b2 will throw an exception if a bare literal is provided.
- // Conforming implementations should also coerce the initial argument to
- // a string prior to parsing.
- if (JSON3.parse("0") === 0 && !JSON3.parse(false)) {
- // Simple parsing test.
- value = JSON3.parse(serialized);
- if ((parseSupported = value.A.length == 5 && value.A[0] == 1)) {
- try {
- // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
- parseSupported = !JSON3.parse('"\t"');
- } catch (exception) {}
- if (parseSupported) {
- try {
- // FF 4.0 and 4.0.1 allow leading `+` signs, and leading and
- // trailing decimal points. FF 4.0, 4.0.1, and IE 9-10 also
- // allow certain octal literals.
- parseSupported = JSON3.parse("01") != 1;
- } catch (exception) {}
- }
- }
- }
- } catch (exception) {
- parseSupported = false;
- }
- }
- if (!all) {
- return parseSupported;
- }
- }
- return stringifySupported && parseSupported;
- }
- }
-
- if (!has("json")) {
- // Define additional utility methods if the `Date` methods are buggy.
- if (!isExtended) {
- floor = Math.floor;
- // A mapping between the months of the year and the number of days between
- // January 1st and the first of the respective month.
- Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
- // Internal: Calculates the number of days between the Unix epoch and the
- // first day of the given month.
- getDay = function (year, month) {
- return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
- };
- }
-
- // Internal: Determines if a property is a direct property of the given
- // object. Delegates to the native `Object#hasOwnProperty` method.
- if (!(isProperty = {}.hasOwnProperty)) {
- isProperty = function (property) {
- var members = {}, constructor;
- if ((members.__proto__ = null, members.__proto__ = {
- // The *proto* property cannot be set multiple times in recent
- // versions of Firefox and SeaMonkey.
- "toString": 1
- }, members).toString != getClass) {
- // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
- // supports the mutable *proto* property.
- isProperty = function (property) {
- // Capture and break the object's prototype chain (see section 8.6.2
- // of the ES 5.1 spec). The parenthesized expression prevents an
- // unsafe transformation by the Closure Compiler.
- var original = this.__proto__, result = property in (this.__proto__ = null, this);
- // Restore the original prototype chain.
- this.__proto__ = original;
- return result;
- };
- } else {
- // Capture a reference to the top-level `Object` constructor.
- constructor = members.constructor;
- // Use the `constructor` property to simulate `Object#hasOwnProperty` in
- // other environments.
- isProperty = function (property) {
- var parent = (this.constructor || constructor).prototype;
- return property in this && !(property in parent && this[property] === parent[property]);
- };
- }
- members = null;
- return isProperty.call(this, property);
- };
- }
-
- // Internal: Normalizes the `for...in` iteration algorithm across
- // environments. Each enumerated key is yielded to a `callback` function.
- forEach = function (object, callback) {
- var size = 0, Properties, members, property, forEach;
-
- // Tests for bugs in the current environment's `for...in` algorithm. The
- // `valueOf` property inherits the non-enumerable flag from
- // `Object.prototype` in older versions of IE, Netscape, and Mozilla.
- (Properties = function () {
- this.valueOf = 0;
- }).prototype.valueOf = 0;
-
- // Iterate over a new instance of the `Properties` class.
- members = new Properties();
- for (property in members) {
- // Ignore all properties inherited from `Object.prototype`.
- if (isProperty.call(members, property)) {
- size++;
- }
- }
- Properties = members = null;
-
- // Normalize the iteration algorithm.
- if (!size) {
- // A list of non-enumerable properties inherited from `Object.prototype`.
- members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
- // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
- // properties.
- forEach = function (object, callback) {
- var isFunction = getClass.call(object) == "[object Function]", property, length;
- for (property in object) {
- // Gecko <= 1.0 enumerates the `prototype` property of functions under
- // certain conditions; IE does not.
- if (!(isFunction && property == "prototype") && isProperty.call(object, property)) {
- callback(property);
- }
- }
- // Manually invoke the callback for each non-enumerable property.
- for (length = members.length; property = members[--length]; isProperty.call(object, property) && callback(property));
- };
- } else if (size == 2) {
- // Safari <= 2.0.4 enumerates shadowed properties twice.
- forEach = function (object, callback) {
- // Create a set of iterated properties.
- var members = {}, isFunction = getClass.call(object) == "[object Function]", property;
- for (property in object) {
- // Store each property name to prevent double enumeration. The
- // `prototype` property of functions is not enumerated due to cross-
- // environment inconsistencies.
- if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
- callback(property);
- }
- }
- };
- } else {
- // No bugs detected; use the standard `for...in` algorithm.
- forEach = function (object, callback) {
- var isFunction = getClass.call(object) == "[object Function]", property, isConstructor;
- for (property in object) {
- if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
- callback(property);
- }
- }
- // Manually invoke the callback for the `constructor` property due to
- // cross-environment inconsistencies.
- if (isConstructor || isProperty.call(object, (property = "constructor"))) {
- callback(property);
- }
- };
- }
- return forEach(object, callback);
- };
-
- // Public: Serializes a JavaScript `value` as a JSON string. The optional
- // `filter` argument may specify either a function that alters how object and
- // array members are serialized, or an array of strings and numbers that
- // indicates which properties should be serialized. The optional `width`
- // argument may be either a string or number that specifies the indentation
- // level of the output.
- if (!has("json-stringify")) {
- // Internal: A map of control characters and their escaped equivalents.
- Escapes = {
- "\\": "\\\\",
- '"': '\\"',
- "\b": "\\b",
- "\f": "\\f",
- "\n": "\\n",
- "\r": "\\r",
- "\t": "\\t"
- };
-
- // Internal: Converts `value` into a zero-padded string such that its
- // length is at least equal to `width`. The `width` must be <= 6.
- toPaddedString = function (width, value) {
- // The `|| 0` expression is necessary to work around a bug in
- // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
- return ("000000" + (value || 0)).slice(-width);
- };
-
- // Internal: Double-quotes a string `value`, replacing all ASCII control
- // characters (characters with code unit values between 0 and 31) with
- // their escaped equivalents. This is an implementation of the
- // `Quote(value)` operation defined in ES 5.1 section 15.12.3.
- quote = function (value) {
- var result = '"', index = 0, symbol;
- for (; symbol = value.charAt(index); index++) {
- // Escape the reverse solidus, double quote, backspace, form feed, line
- // feed, carriage return, and tab characters.
- result += '\\"\b\f\n\r\t'.indexOf(symbol) > -1 ? Escapes[symbol] :
- // If the character is a control character, append its Unicode escape
- // sequence; otherwise, append the character as-is.
- (Escapes[symbol] = symbol < " " ? "\\u00" + toPaddedString(2, symbol.charCodeAt(0).toString(16)) : symbol);
- }
- return result + '"';
- };
-
- // Internal: Recursively serializes an object. Implements the
- // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
- serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
- var value = object[property], className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, any, result;
- if (typeof value == "object" && value) {
- className = getClass.call(value);
- if (className == "[object Date]" && !isProperty.call(value, "toJSON")) {
- if (value > -1 / 0 && value < 1 / 0) {
- // Dates are serialized according to the `Date#toJSON` method
- // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
- // for the ISO 8601 date time string format.
- if (getDay) {
- // Manually compute the year, month, date, hours, minutes,
- // seconds, and milliseconds if the `getUTC*` methods are
- // buggy. Adapted from @Yaffle's `date-shim` project.
- date = floor(value / 864e5);
- for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
- for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
- date = 1 + date - getDay(year, month);
- // The `time` value specifies the time within the day (see ES
- // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
- // to compute `A modulo B`, as the `%` operator does not
- // correspond to the `modulo` operation for negative numbers.
- time = (value % 864e5 + 864e5) % 864e5;
- // The hours, minutes, seconds, and milliseconds are obtained by
- // decomposing the time within the day. See section 15.9.1.10.
- hours = floor(time / 36e5) % 24;
- minutes = floor(time / 6e4) % 60;
- seconds = floor(time / 1e3) % 60;
- milliseconds = time % 1e3;
- } else {
- year = value.getUTCFullYear();
- month = value.getUTCMonth();
- date = value.getUTCDate();
- hours = value.getUTCHours();
- minutes = value.getUTCMinutes();
- seconds = value.getUTCSeconds();
- milliseconds = value.getUTCMilliseconds();
- }
- // Serialize extended years correctly.
- value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
- "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
- // Months, dates, hours, minutes, and seconds should have two
- // digits; milliseconds should have three.
- "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
- // Milliseconds are optional in ES 5.0, but required in 5.1.
- "." + toPaddedString(3, milliseconds) + "Z";
- } else {
- value = null;
- }
- } else if (typeof value.toJSON == "function" && ((className != "[object Number]" && className != "[object String]" && className != "[object Array]") || isProperty.call(value, "toJSON"))) {
- // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
- // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
- // ignores all `toJSON` methods on these objects unless they are
- // defined directly on an instance.
- value = value.toJSON(property);
- }
- }
- if (callback) {
- // If a replacement function was provided, call it to obtain the value
- // for serialization.
- value = callback.call(object, property, value);
- }
- if (value === null) {
- return "null";
- }
- className = getClass.call(value);
- if (className == "[object Boolean]") {
- // Booleans are represented literally.
- return "" + value;
- } else if (className == "[object Number]") {
- // JSON numbers must be finite. `Infinity` and `NaN` are serialized as
- // `"null"`.
- return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
- } else if (className == "[object String]") {
- // Strings are double-quoted and escaped.
- return quote(value);
- }
- // Recursively serialize objects and arrays.
- if (typeof value == "object") {
- // Check for cyclic structures. This is a linear search; performance
- // is inversely proportional to the number of unique nested objects.
- for (length = stack.length; length--;) {
- if (stack[length] === value) {
- // Cyclic structures cannot be serialized by `JSON.stringify`.
- throw TypeError();
- }
- }
- // Add the object to the stack of traversed objects.
- stack.push(value);
- results = [];
- // Save the current indentation level and indent one additional level.
- prefix = indentation;
- indentation += whitespace;
- if (className == "[object Array]") {
- // Recursively serialize array elements.
- for (index = 0, length = value.length; index < length; any || (any = true), index++) {
- element = serialize(index, value, callback, properties, whitespace, indentation, stack);
- results.push(element === undef ? "null" : element);
- }
- result = any ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
- } else {
- // Recursively serialize object members. Members are selected from
- // either a user-specified list of property names, or the object
- // itself.
- forEach(properties || value, function (property) {
- var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
- if (element !== undef) {
- // According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
- // is not the empty string, let `member` {quote(property) + ":"}
- // be the concatenation of `member` and the `space` character."
- // The "`space` character" refers to the literal space
- // character, not the `space` {width} argument provided to
- // `JSON.stringify`.
- results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
- }
- any || (any = true);
- });
- result = any ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
- }
- // Remove the object from the traversed object stack.
- stack.pop();
- return result;
- }
- };
-
- // Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
- JSON3.stringify = function (source, filter, width) {
- var whitespace, callback, properties, index, length, value;
- if (typeof filter == "function" || typeof filter == "object" && filter) {
- if (getClass.call(filter) == "[object Function]") {
- callback = filter;
- } else if (getClass.call(filter) == "[object Array]") {
- // Convert the property names array into a makeshift set.
- properties = {};
- for (index = 0, length = filter.length; index < length; value = filter[index++], ((getClass.call(value) == "[object String]" || getClass.call(value) == "[object Number]") && (properties[value] = 1)));
- }
- }
- if (width) {
- if (getClass.call(width) == "[object Number]") {
- // Convert the `width` to an integer and create a string containing
- // `width` number of space characters.
- if ((width -= width % 1) > 0) {
- for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
- }
- } else if (getClass.call(width) == "[object String]") {
- whitespace = width.length <= 10 ? width : width.slice(0, 10);
- }
- }
- // Opera <= 7.54u2 discards the values associated with empty string keys
- // (`""`) only if they are used directly within an object member list
- // (e.g., `!("" in { "": 1})`).
- return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
- };
- }
-
- // Public: Parses a JSON source string.
- if (!has("json-parse")) {
- fromCharCode = String.fromCharCode;
- // Internal: A map of escaped control characters and their unescaped
- // equivalents.
- Unescapes = {
- "\\": "\\",
- '"': '"',
- "/": "/",
- "b": "\b",
- "t": "\t",
- "n": "\n",
- "f": "\f",
- "r": "\r"
- };
-
- // Internal: Resets the parser state and throws a `SyntaxError`.
- abort = function() {
- Index = Source = null;
- throw SyntaxError();
- };
-
- // Internal: Returns the next token, or `"$"` if the parser has reached
- // the end of the source string. A token may be a string, number, `null`
- // literal, or Boolean literal.
- lex = function () {
- var source = Source, length = source.length, symbol, value, begin, position, sign;
- while (Index < length) {
- symbol = source.charAt(Index);
- if ("\t\r\n ".indexOf(symbol) > -1) {
- // Skip whitespace tokens, including tabs, carriage returns, line
- // feeds, and space characters.
- Index++;
- } else if ("{}[]:,".indexOf(symbol) > -1) {
- // Parse a punctuator token at the current position.
- Index++;
- return symbol;
- } else if (symbol == '"') {
- // Advance to the next character and parse a JSON string at the
- // current position. String tokens are prefixed with the sentinel
- // `@` character to distinguish them from punctuators.
- for (value = "@", Index++; Index < length;) {
- symbol = source.charAt(Index);
- if (symbol < " ") {
- // Unescaped ASCII control characters are not permitted.
- abort();
- } else if (symbol == "\\") {
- // Parse escaped JSON control characters, `"`, `\`, `/`, and
- // Unicode escape sequences.
- symbol = source.charAt(++Index);
- if ('\\"/btnfr'.indexOf(symbol) > -1) {
- // Revive escaped control characters.
- value += Unescapes[symbol];
- Index++;
- } else if (symbol == "u") {
- // Advance to the first character of the escape sequence.
- begin = ++Index;
- // Validate the Unicode escape sequence.
- for (position = Index + 4; Index < position; Index++) {
- symbol = source.charAt(Index);
- // A valid sequence comprises four hexdigits that form a
- // single hexadecimal value.
- if (!(symbol >= "0" && symbol <= "9" || symbol >= "a" && symbol <= "f" || symbol >= "A" && symbol <= "F")) {
- // Invalid Unicode escape sequence.
- abort();
- }
- }
- // Revive the escaped character.
- value += fromCharCode("0x" + source.slice(begin, Index));
- } else {
- // Invalid escape sequence.
- abort();
- }
- } else {
- if (symbol == '"') {
- // An unescaped double-quote character marks the end of the
- // string.
- break;
- }
- // Append the original character as-is.
- value += symbol;
- Index++;
- }
- }
- if (source.charAt(Index) == '"') {
- Index++;
- // Return the revived string.
- return value;
- }
- // Unterminated string.
- abort();
- } else {
- // Parse numbers and literals.
- begin = Index;
- // Advance the scanner's position past the sign, if one is
- // specified.
- if (symbol == "-") {
- sign = true;
- symbol = source.charAt(++Index);
- }
- // Parse an integer or floating-point value.
- if (symbol >= "0" && symbol <= "9") {
- // Leading zeroes are interpreted as octal literals.
- if (symbol == "0" && (symbol = source.charAt(Index + 1), symbol >= "0" && symbol <= "9")) {
- // Illegal octal literal.
- abort();
- }
- sign = false;
- // Parse the integer component.
- for (; Index < length && (symbol = source.charAt(Index), symbol >= "0" && symbol <= "9"); Index++);
- // Floats cannot contain a leading decimal point; however, this
- // case is already accounted for by the parser.
- if (source.charAt(Index) == ".") {
- position = ++Index;
- // Parse the decimal component.
- for (; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
- if (position == Index) {
- // Illegal trailing decimal.
- abort();
- }
- Index = position;
- }
- // Parse exponents.
- symbol = source.charAt(Index);
- if (symbol == "e" || symbol == "E") {
- // Skip past the sign following the exponent, if one is
- // specified.
- symbol = source.charAt(++Index);
- if (symbol == "+" || symbol == "-") {
- Index++;
- }
- // Parse the exponential component.
- for (position = Index; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
- if (position == Index) {
- // Illegal empty exponent.
- abort();
- }
- Index = position;
- }
- // Coerce the parsed value to a JavaScript number.
- return +source.slice(begin, Index);
- }
- // A negative sign may only precede numbers.
- if (sign) {
- abort();
- }
- // `true`, `false`, and `null` literals.
- if (source.slice(Index, Index + 4) == "true") {
- Index += 4;
- return true;
- } else if (source.slice(Index, Index + 5) == "false") {
- Index += 5;
- return false;
- } else if (source.slice(Index, Index + 4) == "null") {
- Index += 4;
- return null;
- }
- // Unrecognized token.
- abort();
- }
- }
- // Return the sentinel `$` character if the parser has reached the end
- // of the source string.
- return "$";
- };
-
- // Internal: Parses a JSON `value` token.
- get = function (value) {
- var results, any, key;
- if (value == "$") {
- // Unexpected end of input.
- abort();
- }
- if (typeof value == "string") {
- if (value.charAt(0) == "@") {
- // Remove the sentinel `@` character.
- return value.slice(1);
- }
- // Parse object and array literals.
- if (value == "[") {
- // Parses a JSON array, returning a new JavaScript array.
- results = [];
- for (;; any || (any = true)) {
- value = lex();
- // A closing square bracket marks the end of the array literal.
- if (value == "]") {
- break;
- }
- // If the array literal contains elements, the current token
- // should be a comma separating the previous element from the
- // next.
- if (any) {
- if (value == ",") {
- value = lex();
- if (value == "]") {
- // Unexpected trailing `,` in array literal.
- abort();
- }
- } else {
- // A `,` must separate each array element.
- abort();
- }
- }
- // Elisions and leading commas are not permitted.
- if (value == ",") {
- abort();
- }
- results.push(get(value));
- }
- return results;
- } else if (value == "{") {
- // Parses a JSON object, returning a new JavaScript object.
- results = {};
- for (;; any || (any = true)) {
- value = lex();
- // A closing curly brace marks the end of the object literal.
- if (value == "}") {
- break;
- }
- // If the object literal contains members, the current token
- // should be a comma separator.
- if (any) {
- if (value == ",") {
- value = lex();
- if (value == "}") {
- // Unexpected trailing `,` in object literal.
- abort();
- }
- } else {
- // A `,` must separate each object member.
- abort();
- }
- }
- // Leading commas are not permitted, object property names must be
- // double-quoted strings, and a `:` must separate each property
- // name and value.
- if (value == "," || typeof value != "string" || value.charAt(0) != "@" || lex() != ":") {
- abort();
- }
- results[value.slice(1)] = get(lex());
- }
- return results;
- }
- // Unexpected token encountered.
- abort();
- }
- return value;
- };
-
- // Internal: Updates a traversed object member.
- update = function(source, property, callback) {
- var element = walk(source, property, callback);
- if (element === undef) {
- delete source[property];
- } else {
- source[property] = element;
- }
- };
-
- // Internal: Recursively traverses a parsed JSON object, invoking the
- // `callback` function for each value. This is an implementation of the
- // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
- walk = function (source, property, callback) {
- var value = source[property], length;
- if (typeof value == "object" && value) {
- if (getClass.call(value) == "[object Array]") {
- for (length = value.length; length--;) {
- update(value, length, callback);
- }
- } else {
- // `forEach` can't be used to traverse an array in Opera <= 8.54,
- // as `Object#hasOwnProperty` returns `false` for array indices
- // (e.g., `![1, 2, 3].hasOwnProperty("0")`).
- forEach(value, function (property) {
- update(value, property, callback);
- });
- }
- }
- return callback.call(source, property, value);
- };
-
- // Public: `JSON.parse`. See ES 5.1 section 15.12.2.
- JSON3.parse = function (source, callback) {
- var result, value;
- Index = 0;
- Source = source;
- result = get(lex());
- // If a JSON string contains multiple tokens, it is invalid.
- if (lex() != "$") {
- abort();
- }
- // Reset the parser state.
- Index = Source = null;
- return callback && getClass.call(callback) == "[object Function]" ? walk((value = {}, value[""] = result, value), "", callback) : result;
- };
- }
- }
-
- // Export for asynchronous module loaders.
- if (isLoader) {
- define(function () {
- return JSON3;
- });
- }
-}).call(this);
\ No newline at end of file
From 9975ebb38bb75dcaa2ac3d1d325b49079eb9b781 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Mon, 3 Dec 2012 11:36:03 -0500
Subject: [PATCH 32/40] reverting 'ensure a bound instance is an instance ...'
---
test/functions.js | 1 -
underscore.js | 11 +++--------
2 files changed, 3 insertions(+), 9 deletions(-)
diff --git a/test/functions.js b/test/functions.js
index dc01f33ab..46128f0bb 100644
--- a/test/functions.js
+++ b/test/functions.js
@@ -37,7 +37,6 @@ $(document).ready(function() {
var newBoundf = new Boundf();
equal(newBoundf.hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
- ok(newBoundf instanceof Boundf && newBoundf instanceof F, "a bound instance is an instance of the bound and original function");
});
test("bindAll", function() {
diff --git a/underscore.js b/underscore.js
index c91e50557..a25c7086f 100644
--- a/underscore.js
+++ b/underscore.js
@@ -574,21 +574,16 @@
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
+ var args, bound;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
- var args = slice.call(arguments, 2);
- var bound = function() {
+ args = slice.call(arguments, 2);
+ return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
var result = func.apply(this, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return this;
};
- if (func && func.prototype) {
- ctor.prototype = func.prototype;
- bound.prototype = new ctor;
- ctor.prototype = null;
- }
- return bound;
};
// Bind all of an object's methods to that object. Useful for ensuring that
From 35796f12eaa36a90cc6edca52fd8712c751c561a Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Mon, 3 Dec 2012 11:39:26 -0500
Subject: [PATCH 33/40] tweak uniqueId
---
underscore.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/underscore.js b/underscore.js
index a25c7086f..67b641e51 100644
--- a/underscore.js
+++ b/underscore.js
@@ -1070,8 +1070,8 @@
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
- var id = idCounter++;
- return '' + (prefix ? '' + prefix + id : id);
+ var id = '' + ++idCounter;
+ return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
From 16a229ae6641aa645ee76371a5c0bd7c78e42416 Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Mon, 3 Dec 2012 23:03:02 -0800
Subject: [PATCH 34/40] Finish the previous `_.bind` revert.
---
test/functions.js | 1 +
underscore.js | 9 ++++++---
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/test/functions.js b/test/functions.js
index 46128f0bb..b3e81ed7a 100644
--- a/test/functions.js
+++ b/test/functions.js
@@ -37,6 +37,7 @@ $(document).ready(function() {
var newBoundf = new Boundf();
equal(newBoundf.hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
+ ok(newBoundf instanceof F, "a bound instance is an instance of the original function");
});
test("bindAll", function() {
diff --git a/underscore.js b/underscore.js
index 67b641e51..64f6b6d4e 100644
--- a/underscore.js
+++ b/underscore.js
@@ -573,16 +573,19 @@
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
- _.bind = function bind(func, context) {
+ _.bind = function(func, context) {
var args, bound;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
- var result = func.apply(this, args.concat(slice.call(arguments)));
+ ctor.prototype = func.prototype;
+ var self = new ctor;
+ ctor.prototype = null;
+ var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
- return this;
+ return self;
};
};
From d24943443672cd3a6b401d83983298576db1e3ac Mon Sep 17 00:00:00 2001
From: John-David Dalton
Date: Mon, 3 Dec 2012 23:15:49 -0800
Subject: [PATCH 35/40] Ensure `_.throttle` triggers trailing call after
repeatedly invoked. [closes #882]
---
test/functions.js | 25 +++++++++++++++++++++++++
underscore.js | 1 +
2 files changed, 26 insertions(+)
diff --git a/test/functions.js b/test/functions.js
index b3e81ed7a..de141729c 100644
--- a/test/functions.js
+++ b/test/functions.js
@@ -165,6 +165,31 @@ $(document).ready(function() {
}, 400);
});
+ asyncTest("throttle triggers trailing call after repeatedly invoked", 2, function() {
+ var actual;
+ var counter = 0;
+ var limit = 80;
+ var incr = function(){ counter++; };
+ var throttledIncr = _.throttle(incr, 32);
+
+ var stamp = new Date;
+ while ((new Date - stamp) < limit) {
+ throttledIncr();
+ }
+ _.delay(function() {
+ actual = counter + 2;
+ throttledIncr();
+ throttledIncr();
+ }, 64);
+
+ _.delay(function() {
+ equal(counter, actual);
+ start();
+ }, 128);
+
+ ok(counter > 1);
+ });
+
asyncTest("debounce", 1, function() {
var counter = 0;
var incr = function(){ counter++; };
diff --git a/underscore.js b/underscore.js
index 64f6b6d4e..dafdd19b8 100644
--- a/underscore.js
+++ b/underscore.js
@@ -638,6 +638,7 @@
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
+ timeout = null;
previous = now;
result = func.apply(context, args);
} else if (!timeout) {
From 9a5a4b209419e7290dd3872499e0d43aea31bd7c Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Tue, 4 Dec 2012 12:53:33 -0500
Subject: [PATCH 36/40] A byte a day keeps the doctor away.
---
underscore.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/underscore.js b/underscore.js
index 67b641e51..43e3a6941 100644
--- a/underscore.js
+++ b/underscore.js
@@ -102,6 +102,8 @@
return results;
};
+ var reduceError = 'Reduce of empty array with no initial value';
+
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
@@ -119,7 +121,7 @@
memo = iterator.call(context, memo, value, index, list);
}
});
- if (!initial) throw new TypeError('Reduce of empty array with no initial value');
+ if (!initial) throw new TypeError(reduceError);
return memo;
};
@@ -146,7 +148,7 @@
memo = iterator.call(context, memo, obj[index], index, list);
}
});
- if (!initial) throw new TypeError('Reduce of empty array with no initial value');
+ if (!initial) throw new TypeError(reduceError);
return memo;
};
From 9d02f624027dae31ba1459201634fe6664e7f920 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Tue, 4 Dec 2012 13:17:01 -0500
Subject: [PATCH 37/40] tiny tweak to any
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index 675d2df1d..cc89b2646 100644
--- a/underscore.js
+++ b/underscore.js
@@ -207,7 +207,7 @@
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
- if (result || (result = iterator.call(context, value, index, list))) return breaker;
+ if (result = iterator.call(context, value, index, list)) return breaker;
});
return !!result;
};
From 6a47202661494594569fbbe1e83b7e24087901fe Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Tue, 4 Dec 2012 13:21:19 -0500
Subject: [PATCH 38/40] Revert "tiny tweak to any"
This reverts commit 9d02f624027dae31ba1459201634fe6664e7f920.
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index cc89b2646..675d2df1d 100644
--- a/underscore.js
+++ b/underscore.js
@@ -207,7 +207,7 @@
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
- if (result = iterator.call(context, value, index, list)) return breaker;
+ if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
From b1f1037ed1079b803986d020df6b342aabbbb9ed Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Tue, 4 Dec 2012 13:21:57 -0500
Subject: [PATCH 39/40] removing overly cautious hasOwnProperty check from
_.where
---
underscore.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/underscore.js b/underscore.js
index 675d2df1d..19f2ec7dc 100644
--- a/underscore.js
+++ b/underscore.js
@@ -241,7 +241,7 @@
if (_.isEmpty(attrs)) return [];
return _.filter(obj, function(value) {
for (var key in attrs) {
- if (_.has(attrs, key) && attrs[key] !== value[key]) return false;
+ if (attrs[key] !== value[key]) return false;
}
return true;
});
From 78887cffb53ed372811b5cc1239e0ecdf701a5c8 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Date: Tue, 4 Dec 2012 13:48:01 -0500
Subject: [PATCH 40/40] Underscore 1.4.3
---
Rakefile | 9 +---
docs/underscore.html | 125 +++++++++++++++++++++++++------------------
index.html | 35 ++++++++++--
package.json | 2 +-
test/index.html | 2 +-
underscore-min.js | 6 +--
underscore.js | 9 ++--
7 files changed, 112 insertions(+), 76 deletions(-)
diff --git a/Rakefile b/Rakefile
index b9fd1ca46..45edbd923 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,11 +1,6 @@
-require 'rubygems'
-require 'uglifier'
-
-desc "Use the Closure Compiler to compress Underscore.js"
+desc "Use Uglify JS to compress Underscore.js"
task :build do
- source = File.read('underscore.js')
- min = Uglifier.compile(source)
- File.open('underscore-min.js', 'w') {|f| f.write min }
+ sh "uglifyjs underscore.js -c -m -o underscore-min.js"
end
desc "Build the docco documentation"
diff --git a/docs/underscore.html b/docs/underscore.html
index 1f4b86a94..384ab1000 100644
--- a/docs/underscore.html
+++ b/docs/underscore.html
@@ -1,11 +1,10 @@
- underscore.js
Underscore.js 1.4.3
http://underscorejs.org
(c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
Underscore may be freely distributed under the MIT license.
The cornerstone, an each implementation, aka forEach.
Handles objects with the built-in forEach, arrays, and raw objects.
Delegates to ECMAScript 5's native forEach if available.
Get the first element of an array. Passing n will return the first N
values in the array. Aliased as head and take. The guard check
allows it to work with _.map.
Returns everything but the last entry of the array. Especially useful on
the arguments object. Passing n will return all the values in
@@ -275,6 +274,7 @@
returnslice.call(array,0,array.length-((n==null)||guard?1:n));};
Get the last element of an array. Passing n will return the last N
values in the array. The guard check allows it to work with _.map.
_.last=function(array,n,guard){
+ if(array==null)returnvoid0;if((n!=null)&&!guard){returnslice.call(array,Math.max(array.length-n,0));}else{
@@ -286,7 +286,7 @@
check allows it to work with _.map.
Produce a duplicate-free version of the array. If the array has already
been sorted, you have the option of using a faster algorithm.
Aliased as unique.
Converts lists into objects. Pass either a single array of [key, value]
pairs, or two parallel arrays of the same length -- one of keys, and one of
the corresponding values.
Create a function bound to a given object (assigning this, and arguments,
optionally). Binding with arguments is also known as curry.
Delegates to ECMAScript 5's native Function.bind if available.
-We check for func.bind first, to fail fast when func is undefined.
_.bind=functionbind(func,context){
- varbound,args;
+We check for func.bind first, to fail fast when func is undefined.