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.

 _(3).times(function(n){ genie.grantWishNumber(n); });
From bb97a4d9f79712fed274f692a05a2828e1f8bba4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 9 Oct 2012 14:10:15 -0400 Subject: [PATCH 03/40] Fixes #820 -- throttle function uses two timeouts. --- underscore.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/underscore.js b/underscore.js index 43b4e407d..617092d89 100644 --- a/underscore.js +++ b/underscore.js @@ -622,25 +622,25 @@ // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = _.debounce(function(){ more = throttling = false; }, wait); + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) { - result = func.apply(context, args); - } - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - throttling = true; + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + previous = now; result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); } - whenDone(); return result; }; }; From 7419c805b0e1f40c1a5394bc928f707ad4c6ed78 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 10 Oct 2012 11:05:31 -0400 Subject: [PATCH 04/40] Fixes #822 -- implements reject in terms of select --- test/collections.js | 8 ++++++++ underscore.js | 9 +++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test/collections.js b/test/collections.js index 7dce44ba5..e089626df 100644 --- a/test/collections.js +++ b/test/collections.js @@ -171,6 +171,14 @@ $(document).ready(function() { test('reject', function() { var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equal(odds.join(', '), '1, 3, 5', 'rejected each even number'); + + var context = "obj"; + + var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){ + equal(context, "obj"); + return num % 2 != 0; + }, context); + equal(evens.join(', '), '2, 4, 6', 'rejected each odd number'); }); test('all', function() { diff --git a/underscore.js b/underscore.js index 617092d89..65b59c0cd 100644 --- a/underscore.js +++ b/underscore.js @@ -177,12 +177,9 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); }; // Determine whether all of the elements match a truth test. From 143d1373064f79757fdb9d484244e38ba951ea78 Mon Sep 17 00:00:00 2001 From: Tal Ater Date: Sat, 20 Oct 2012 18:56:08 +0200 Subject: [PATCH 05/40] Updated _.isFinite to match ES5 and ES6 spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EcmaScript 5 and 6 spec define isFinite as returning false only if ToNumber(number) is NaN, Infinity, or −Infinity. Current implementation of _.isFinite acts as if ToNumber('42') is NaN. This commit fixes it while also catching the special gotcha in the spec where isFinite(' ') returns true. It's also much faster than current implementation. --- test/objects.js | 4 +++- underscore.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/objects.js b/test/objects.js index 22949c3bf..ecb9ae4ac 100644 --- a/test/objects.js +++ b/test/objects.js @@ -485,7 +485,9 @@ $(document).ready(function() { ok(!_.isFinite(NaN), 'NaN is not Finite'); ok(!_.isFinite(Infinity), 'Infinity is not Finite'); ok(!_.isFinite(-Infinity), '-Infinity is not Finite'); - ok(!_.isFinite('12'), 'Strings are not numbers'); + ok(_.isFinite('12'), 'Numeric strings are numbers'); + ok(!_.isFinite('1a'), 'Non numeric strings are not numbers'); + ok(!_.isFinite(''), 'Empty strings are not numbers'); var obj = new Number(5); ok(_.isFinite(obj), 'Number instances can be finite'); ok(_.isFinite(0), '0 is Finite'); diff --git a/underscore.js b/underscore.js index 65b59c0cd..db899df47 100644 --- a/underscore.js +++ b/underscore.js @@ -951,7 +951,7 @@ // Is a given object a finite number? _.isFinite = function(obj) { - return _.isNumber(obj) && isFinite(obj); + return isFinite( obj ) && !isNaN( parseFloat(obj) ); }; // Is the given value `NaN`? (NaN is the only number which does not equal itself). From a16d61e227fa73c0c2b56db54a23b9f33d719ce3 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sun, 21 Oct 2012 08:13:22 -0400 Subject: [PATCH 06/40] Fix #834 - Provide documentation for _.unescape. --- index.html | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index 9a126159d..88aa54a65 100644 --- a/index.html +++ b/index.html @@ -298,6 +298,7 @@
  • - mixin
  • - uniqueId
  • - escape
  • +
  • - unescape
  • - result
  • - template
  • @@ -627,12 +628,12 @@

    Collection Functions (Arrays or Objects)


    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.

    -_.countBy([1, 2, 3, 4, 5], function(num) { 
    -  return num % 2 == 0 ? 'even' : 'odd'; 
    +_.countBy([1, 2, 3, 4, 5], function(num) {
    +  return num % 2 == 0 ? 'even' : 'odd';
     });
     => {odd: 3, even: 2}
     
    @@ -827,7 +828,7 @@

    Array Functions

    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 &amp; Moe"
    +

    + unescape_.unescape(string) +
    + The opposite of escape, replaces escaped + HTML entities with with their unescaped counterparts. +

    +
    +_.escape('Curly, Larry &amp; Moe');
    +=> "Curly, Larry & Moe"
    +

    result_.result(object, property)
    @@ -1748,18 +1759,18 @@

    Change Log

    - +

    1.4.2Oct. 1, 2012Diff

    • - 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.

    - +

    1.4.1Oct. 1, 2012Diff

      @@ -1768,14 +1779,14 @@

      Change Log

    - +

    1.4.0Sept. 27, 2012Diff

    • - 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.
    • From 1d8ef64c92ff77531586daaa8631e23e9eeb15ef Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 22 Oct 2012 11:32:25 -0400 Subject: [PATCH 07/40] Specify unescaped entities. --- index.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 88aa54a65..bfea75d4d 100644 --- a/index.html +++ b/index.html @@ -1503,8 +1503,10 @@

      Utility Functions

      unescape_.unescape(string)
      - The opposite of escape, replaces escaped - HTML entities with with their unescaped counterparts. + The opposite of escape, replaces + &amp;, &lt;, &gt;, + &quot;, &#x27;, and &#x2F; + with their unescaped counterparts.

       _.escape('Curly, Larry &amp; 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 @@
             
    • - filter
    • - where
    • - reject
    • -
    • - all
    • -
    • - any
    • +
    • - every
    • +
    • - some
    • - contains
    • - invoke
    • - pluck
    • @@ -507,21 +507,21 @@

      Collection Functions (Arrays or Objects)

      => [1, 3, 5]
      -

      - 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.

      -_.all([true, 1, null, 'yes'], _.identity);
      +_.every([true, 1, null, 'yes'], _.identity);
       => false
       
      -

      - 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 @@

      Collection Functions (Arrays or Objects)

      if present.

      -_.any([null, 0, 'yes', false]);
      +_.some([null, 0, 'yes', false]);
       => true
       
      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

    Underscore.js 1.4.2
    +      underscore.js           

    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.
     
    (function() {

    Baseline setup

    Establish the root object, window in the browser, or global on the server.

      var root = this;

    Save the previous value of the _ variable.

      var previousUnderscore = root._;

    Establish the object that gets returned to break out of a loop iteration.

      var breaker = {};

    Save bytes in the minified (but not gzipped) version:

      var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

    Create quick reference variables for speed access to core prototypes.

      var push             = ArrayProto.push,
           slice            = ArrayProto.slice,
           concat           = ArrayProto.concat,
    -      unshift          = ArrayProto.unshift,
           toString         = ObjProto.toString,
           hasOwnProperty   = ObjProto.hasOwnProperty;

    All ECMAScript 5 native function implementations that we hope to use are declared here.

      var
    @@ -33,8 +32,8 @@
         }
         exports._ = _;
       } else {
    -    root['_'] = _;
    -  }

    Current version.

      _.VERSION = '1.4.2';

    Collection Functions

    The cornerstone, an each implementation, aka forEach. + root._ = _; + }

    Current version.

      _.VERSION = '1.4.3';

    Collection Functions

    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.

      var each = _.each = _.forEach = function(obj, iterator, context) {
         if (obj == null) return;
    @@ -60,7 +59,9 @@
           results[results.length] = iterator.call(context, value, index, list);
         });
         return results;
    -  };

    Reduce builds up a single result from a list of values, aka inject, + }; + + 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) {
         var initial = arguments.length > 2;
         if (obj == null) obj = [];
    @@ -76,7 +77,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;
       };

    The right-associative version of reduce, also known as foldr. Delegates to ECMAScript 5's native reduceRight if available.

      _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
    @@ -84,7 +85,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) {
    @@ -100,7 +101,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;
       };

    Return the first value which passes a truth test. Aliased as detect.

      _.find = _.detect = function(obj, iterator, context) {
         var result;
    @@ -122,12 +123,9 @@
         });
         return results;
       };

    Return all the elements for which a truth test fails.

      _.reject = function(obj, iterator, context) {
    -    var results = [];
    -    if (obj == null) return results;
    -    each(obj, function(value, index, list) {
    -      if (!iterator.call(context, value, index, list)) results[results.length] = value;
    -    });
    -    return results;
    +    return _.filter(obj, function(value, index, list) {
    +      return !iterator.call(context, value, index, list);
    +    }, context);
       };

    Determine whether all of the elements match a truth test. Delegates to ECMAScript 5's native every if available. Aliased as all.

      _.every = _.all = function(obj, iterator, context) {
    @@ -152,13 +150,11 @@
         return !!result;
       };

    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.

      _.invoke = function(obj, method) {
         var args = slice.call(arguments, 2);
         return _.map(obj, function(value) {
    @@ -182,7 +178,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});
    @@ -193,7 +189,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});
    @@ -230,7 +226,7 @@
         }), 'value');
       };

    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);
    @@ -244,7 +240,7 @@
       };

    Counts instances of an object that group by a certain criterion. Pass either a string attribute to count by, or a function that returns the criterion.

      _.countBy = function(obj, value, context) {
    -    return group(obj, value, context, function(result, key, value) {
    +    return group(obj, value, context, function(result, key) {
           if (!_.has(result, key)) result[key] = 0;
           result[key]++;
         });
    @@ -260,13 +256,16 @@
         return low;
       };

    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);
       };

    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;
       };

    Array Functions

    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.

      _.first = _.head = _.take = function(array, n, guard) {
    +    if (array == null) return void 0;
         return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
       };

    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 @@ return slice.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) return void 0;
         if ((n != null) && !guard) {
           return slice.call(array, Math.max(array.length - n, 0));
         } else {
    @@ -286,7 +286,7 @@
     check allows it to work with _.map.

      _.rest = _.tail = _.drop = function(array, n, guard) {
         return slice.call(array, (n == null) || guard ? 1 : n);
       };

    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.

      var flatten = function(input, shallow, output) {
         each(input, function(value) {
           if (_.isArray(value)) {
    @@ -303,6 +303,11 @@
       };

    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.

      _.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 = [];
    @@ -340,6 +345,7 @@
       };

    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.

      _.object = function(list, values) {
    +    if (list == null) return {};
         var result = {};
         for (var i = 0, l = list.length; i < l; i++) {
           if (values) {
    @@ -399,8 +405,8 @@
       };

    Function (ahem) Functions

    Reusable constructor function for prototype setting.

      var ctor = function(){};

    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 = function bind(func, context) {
    -    var bound, args;
    +We check for func.bind first, to fail fast when func is undefined.

      _.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);
    @@ -408,6 +414,7 @@
           if (!(this instanceof bound)) return func.apply(context, 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 self;
    @@ -434,25 +441,26 @@
         return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
       };

    Returns a function, that, when invoked, will only be triggered at most once during a given window of time.

      _.throttle = function(func, wait) {
    -    var context, args, timeout, throttling, more, result;
    -    var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
    +    var context, args, timeout, result;
    +    var previous = 0;
    +    var later = function() {
    +      previous = new Date;
    +      timeout = null;
    +      result = func.apply(context, args);
    +    };
         return function() {
    -      context = this; args = arguments;
    -      var later = function() {
    +      var now = new Date;
    +      var remaining = wait - (now - previous);
    +      context = this;
    +      args = arguments;
    +      if (remaining <= 0) {
    +        clearTimeout(timeout);
             timeout = null;
    -        if (more) {
    -          result = func.apply(context, args);
    -        }
    -        whenDone();
    -      };
    -      if (!timeout) timeout = setTimeout(later, wait);
    -      if (throttling) {
    -        more = true;
    -      } else {
    -        throttling = true;
    +        previous = now;
             result = func.apply(context, args);
    +      } else if (!timeout) {
    +        timeout = setTimeout(later, remaining);
           }
    -      whenDone();
           return result;
         };
       };

    Returns a function, that, as long as it continues to be invoked, will not @@ -534,8 +542,10 @@ return names.sort(); };

    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;
    @@ -555,8 +565,10 @@
         return copy;
       };

    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;
    @@ -643,7 +655,7 @@
           return typeof obj === 'function';
         };
       }

    Is a given object a finite number?

      _.isFinite = function(obj) {
    -    return _.isNumber(obj) && isFinite(obj);
    +    return isFinite( obj ) && !isNaN( parseFloat(obj) );
       };

    Is the given value NaN? (NaN is the only number which does not equal itself).

      _.isNaN = function(obj) {
         return _.isNumber(obj) && obj != +obj;
       };

    Is a given value a boolean?

      _.isBoolean = function(obj) {
    @@ -662,7 +674,9 @@
       };

    Keep the identity function around for default iterators.

      _.identity = function(value) {
         return value;
       };

    Run a function n times.

      _.times = function(n, iterator, context) {
    -    for (var i = 0; i < n; i++) iterator.call(context, i);
    +    var accum = Array(n);
    +    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
    +    return accum;
       };

    Return a random integer between min and max (inclusive).

      _.random = function(min, max) {
         if (max == null) {
           max = min;
    @@ -706,7 +720,7 @@
       };

    Generate a unique integer id (unique within the entire client session). Useful for temporary DOM ids.

      var idCounter = 0;
       _.uniqueId = function(prefix) {
    -    var id = idCounter++;
    +    var id = '' + ++idCounter;
         return prefix ? prefix + id : id;
       };

    By default, Underscore uses ERB-style template delimiters, change the following template settings to use alternative delimiters.

      _.templateSettings = {
    @@ -738,11 +752,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";

    If a variable is not specified, place data values in local scope.

        if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
     
    diff --git a/index.html b/index.html
    index 8940d2a79..e62aa9246 100644
    --- a/index.html
    +++ b/index.html
    @@ -179,7 +179,7 @@