From 39856b8e91e69ac7b3a905e1cb591f81bbbcad88 Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Wed, 25 Jun 2014 19:32:49 +0100 Subject: [PATCH 01/11] added binary search for collections Added `search` method to collection prototype. automatically used by `where` and `findWhere` when it is possible to do so. user may provide: - a comparison function (comparator unused) - a value to search for (the value of the model[comparator] sought - requires comparator to be a string) - a model to search for (where `comparator` is a function) Users may also request that it is the index of the model that is returned or that the model of highest/lowest index which matches the comparison is sought. One point that may need to be considered is that the behaviour when a model is input as `toFind` but the `comparator` is a string is different from that when `comparator` is a function. --- backbone.js | 73 +++++++++++++++++++++++++++++++++++++++++----- test/collection.js | 61 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 10 deletions(-) diff --git a/backbone.js b/backbone.js index cedcf04a3..fcd29d12f 100644 --- a/backbone.js +++ b/backbone.js @@ -828,16 +828,75 @@ return this.models[index]; }, + // Perform a binary search for a model in a sorted collection. + search: function (toFind, options) { + if (!this.comparator) throw new Error('Cannot search an unsorted Collection'); + options || (options = {}); + + // Create `compare` function for searching. + var compare; + if (_.isFunction(toFind)) { + // User provided the `compare` function. + compare = toFind; + } else if (_.isFunction(this.comparator)) { + // Use the comparator function, `toFind` is a model. + if (this.comparator.length === 2) { + compare = _(this.comparator).bind(this, toFind); + } else { + compare = _(function (valResult, model) { + var modelResult = this.comparator(model); + return valResult === modelResult ? 0 : (valResult > modelResult ? 1 : -1); + }).bind(this, this.comparator(toFind)); + } + } else { + // Comparator is a string indicating the model property used to sort + // `toFind` is value sought. + compare = _(function (model) { + var modelValue = model.get(this.comparator); + return toFind === modelValue ? 0 : (toFind > modelValue ? 1 : -1); + }).bind(this); + } + + // Perform binary search. + var found = false, max = this.length - 1, min = 0, index, relValue; + while (max >= min && !found) { + index = Math.floor((max + min) / 2); + relValue = compare(this.at(index)); + if (relValue > 0) { + min = index + 1; + } else if (relValue < 0) { + max = index - 1; + } else if (options.getMax && index < max && compare(this.at(index + 1)) === 0) { + min = index + 1; + } else if (options.getMin && index > min && compare(this.at(index - 1)) === 0) { + max = index - 1; + } else { + found = true; + } + } + if (!found) return options.returnIndex ? -1 : void 0; + return options.returnIndex ? index : this.at(index); + }, + // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { - if (_.isEmpty(attrs)) return first ? void 0 : []; - return this[first ? 'find' : 'filter'](function(model) { - for (var key in attrs) { - if (attrs[key] !== model.get(key)) return false; - } - return true; - }); + if (_.isEmpty(attrs)) { + return first ? void 0 : []; + } else if (!_.isEqual(_.keys(attrs), [this.comparator])) { + return this[first ? 'find' : 'filter'](function(model) { + for (var key in attrs) { + if (attrs[key] !== model.get(key)) return false; + } + return true; + }); + } else if (first) { + return [this.search(attrs[this.comparator], {getMin: true})]; + } else { + var minIndex = this.search(attrs[this.comparator], {returnIndex: true, getMin: true}); + var maxIndex = this.search(attrs[this.comparator], {returnIndex: true, getMax: true}); + return !minIndex ? [] : this.models.slice(minIndex, maxIndex + 1); + } }, // Return the first model with matching attributes. Useful for simple cases diff --git a/test/collection.js b/test/collection.js index 8f518143d..be5ebe8b2 100644 --- a/test/collection.js +++ b/test/collection.js @@ -547,15 +547,70 @@ equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]'); }); - test("where and findWhere", 8, function() { + test("search", 12, function() { + var model1 = new Backbone.Model({a: 0}); + var model2 = new Backbone.Model({a: 3}); + var models = [ + model1, {a: 1}, {a: 1}, {a: 1}, {a: 2}, model2 + ]; + var coll = new Backbone.Collection(models, { + comparator: 'a' + }); + + equal(coll.search(1).get('a'), 1); + equal(coll.search(1, {getMin: true}), coll.at(1)); + equal(coll.search(1, {getMax: true}), coll.at(3)); + equal(coll.search(2, {returnIndex: true}), 4); + equal(coll.search(3, {returnIndex: true}), 5); + equal(coll.search(4, {returnIndex: true}), -1); + ok(!coll.search(4)); + + equal(coll.search(function (model) { + var v = model.get('a'); + if (v < 2) return 1; + if (v > 2) return -1; + return 0; + }).get('a'), 2); + + var col2 = new Backbone.Collection(models, { + comparator: function (m) { + return 5 - m.get('a'); + } + }); + equal(col2.search(model1, {returnIndex: true}), 5); + equal(col2.search(model2, {returnIndex: true}), 0); + + var col3 = new Backbone.Collection(models, { + comparator: function (m1, m2) { + return m2.get('a') - m1.get('a'); + } + }); + equal(col3.search(model1), model1); + equal(col3.search(model2), model2); + }); + + test("where and findWhere", 16, function() { var model = new Backbone.Model({a: 1}); - var coll = new Backbone.Collection([ + var models = [ model, {a: 1}, {a: 1, b: 2}, {a: 2, b: 2}, {a: 3} - ]); + ]; + var coll = new Backbone.Collection(models); + equal(coll.where({a: 1}).length, 3); + equal(coll.where({a: 2}).length, 1); + equal(coll.where({a: 3}).length, 1); + equal(coll.where({b: 1}).length, 0); + equal(coll.where({b: 2}).length, 2); + equal(coll.where({a: 1, b: 2}).length, 1); + equal(coll.findWhere({a: 1}), model); + equal(coll.findWhere({a: 4}), void 0); + + var col2 = new Backbone.Collection(models, { + comparator: 'a' + }); equal(coll.where({a: 1}).length, 3); equal(coll.where({a: 2}).length, 1); equal(coll.where({a: 3}).length, 1); From 39ffec8c6b8b2a6e8a5333be37e048d8364d11bf Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Wed, 25 Jun 2014 19:53:21 +0100 Subject: [PATCH 02/11] fixed binary search Fixed small bugs with `where` and `findWhere` and the erroneous tests which didn't catch these issues. --- backbone.js | 4 ++-- test/collection.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backbone.js b/backbone.js index fcd29d12f..991583037 100644 --- a/backbone.js +++ b/backbone.js @@ -891,11 +891,11 @@ return true; }); } else if (first) { - return [this.search(attrs[this.comparator], {getMin: true})]; + return this.search(attrs[this.comparator], {getMin: true}); } else { var minIndex = this.search(attrs[this.comparator], {returnIndex: true, getMin: true}); var maxIndex = this.search(attrs[this.comparator], {returnIndex: true, getMax: true}); - return !minIndex ? [] : this.models.slice(minIndex, maxIndex + 1); + return minIndex == null ? [] : this.models.slice(minIndex, maxIndex + 1); } }, diff --git a/test/collection.js b/test/collection.js index be5ebe8b2..7cdabd333 100644 --- a/test/collection.js +++ b/test/collection.js @@ -611,14 +611,14 @@ var col2 = new Backbone.Collection(models, { comparator: 'a' }); - equal(coll.where({a: 1}).length, 3); - equal(coll.where({a: 2}).length, 1); - equal(coll.where({a: 3}).length, 1); - equal(coll.where({b: 1}).length, 0); - equal(coll.where({b: 2}).length, 2); - equal(coll.where({a: 1, b: 2}).length, 1); - equal(coll.findWhere({a: 1}), model); - equal(coll.findWhere({a: 4}), void 0); + equal(col2.where({a: 1}).length, 3); + equal(col2.where({a: 2}).length, 1); + equal(col2.where({a: 3}).length, 1); + equal(col2.where({b: 1}).length, 0); + equal(col2.where({b: 2}).length, 2); + equal(col2.where({a: 1, b: 2}).length, 1); + equal(col2.findWhere({a: 1}), model); + equal(col2.findWhere({a: 4}), void 0); }); test("Underscore methods", 16, function() { From bdbc9bd9e1bcebf430fe2d301a9d9fa6dca351ef Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Thu, 26 Jun 2014 01:25:44 +0100 Subject: [PATCH 03/11] alter code to better fit previous style --- backbone.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backbone.js b/backbone.js index 991583037..361d25a93 100644 --- a/backbone.js +++ b/backbone.js @@ -836,25 +836,25 @@ // Create `compare` function for searching. var compare; if (_.isFunction(toFind)) { - // User provided the `compare` function. + // The user has provided the `compare` function. compare = toFind; } else if (_.isFunction(this.comparator)) { // Use the comparator function, `toFind` is a model. if (this.comparator.length === 2) { - compare = _(this.comparator).bind(this, toFind); + compare = _.bind(this.comparator, this, toFind); } else { - compare = _(function (valResult, model) { + compare = _.bind(function (valResult, model) { var modelResult = this.comparator(model); return valResult === modelResult ? 0 : (valResult > modelResult ? 1 : -1); - }).bind(this, this.comparator(toFind)); + }, this, this.comparator(toFind)); } } else { - // Comparator is a string indicating the model property used to sort - // `toFind` is value sought. - compare = _(function (model) { + // `comparator` is a string indicating the model property used to sort + // `toFind` is the value sought. + compare = _.bind(function (model) { var modelValue = model.get(this.comparator); return toFind === modelValue ? 0 : (toFind > modelValue ? 1 : -1); - }).bind(this); + }, this); } // Perform binary search. From c64e5d84013258c73e4db437aa9ffdcec06c3d73 Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Thu, 26 Jun 2014 01:31:04 +0100 Subject: [PATCH 04/11] add style comment backticks --- backbone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backbone.js b/backbone.js index 361d25a93..46b5e7a33 100644 --- a/backbone.js +++ b/backbone.js @@ -839,7 +839,7 @@ // The user has provided the `compare` function. compare = toFind; } else if (_.isFunction(this.comparator)) { - // Use the comparator function, `toFind` is a model. + // Use the `comparator` function, `toFind` is a model. if (this.comparator.length === 2) { compare = _.bind(this.comparator, this, toFind); } else { From 8a04f02ce23246eaa8a99eebcfc794592b351579 Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Thu, 21 Aug 2014 21:38:56 +0100 Subject: [PATCH 05/11] bind `toFind` and only return index If `toFind` is a function, the `compare` method will be bound to the model. Removed option to return model and always return index. --- backbone.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backbone.js b/backbone.js index 46b5e7a33..a678a25db 100644 --- a/backbone.js +++ b/backbone.js @@ -837,7 +837,7 @@ var compare; if (_.isFunction(toFind)) { // The user has provided the `compare` function. - compare = toFind; + compare = _.bind(toFind, this); } else if (_.isFunction(this.comparator)) { // Use the `comparator` function, `toFind` is a model. if (this.comparator.length === 2) { @@ -874,8 +874,8 @@ found = true; } } - if (!found) return options.returnIndex ? -1 : void 0; - return options.returnIndex ? index : this.at(index); + + return found ? index : -1; }, // Return models with matching attributes. Useful for simple cases of From 2c17b4e44fdfff7bdfdeef3711ecebeb7390d24e Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Thu, 21 Aug 2014 21:59:14 +0100 Subject: [PATCH 06/11] fixed bugs and tests from last commit --- backbone.js | 6 +++--- test/collection.js | 25 ++++++++++--------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/backbone.js b/backbone.js index a678a25db..ae146cfb6 100644 --- a/backbone.js +++ b/backbone.js @@ -891,10 +891,10 @@ return true; }); } else if (first) { - return this.search(attrs[this.comparator], {getMin: true}); + return this.at(this.search(attrs[this.comparator], {getMin: true})); } else { - var minIndex = this.search(attrs[this.comparator], {returnIndex: true, getMin: true}); - var maxIndex = this.search(attrs[this.comparator], {returnIndex: true, getMax: true}); + var minIndex = this.search(attrs[this.comparator], {getMin: true}); + var maxIndex = this.search(attrs[this.comparator], {getMax: true}); return minIndex == null ? [] : this.models.slice(minIndex, maxIndex + 1); } }, diff --git a/test/collection.js b/test/collection.js index 7cdabd333..42fc31638 100644 --- a/test/collection.js +++ b/test/collection.js @@ -547,7 +547,7 @@ equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]'); }); - test("search", 12, function() { + test("search", 7, function() { var model1 = new Backbone.Model({a: 0}); var model2 = new Backbone.Model({a: 3}); var models = [ @@ -557,36 +557,31 @@ comparator: 'a' }); - equal(coll.search(1).get('a'), 1); - equal(coll.search(1, {getMin: true}), coll.at(1)); - equal(coll.search(1, {getMax: true}), coll.at(3)); - equal(coll.search(2, {returnIndex: true}), 4); - equal(coll.search(3, {returnIndex: true}), 5); - equal(coll.search(4, {returnIndex: true}), -1); - ok(!coll.search(4)); - - equal(coll.search(function (model) { + equal(coll.at(coll.search(1)).get('a'), 1); + equal(coll.search(1, {getMin: true}), 1); + equal(coll.search(1, {getMax: true}), 3); + ok(coll.search(4) === -1); + + equal(coll.at(coll.search(function (model) { var v = model.get('a'); if (v < 2) return 1; if (v > 2) return -1; return 0; - }).get('a'), 2); + })).get('a'), 2); var col2 = new Backbone.Collection(models, { comparator: function (m) { return 5 - m.get('a'); } }); - equal(col2.search(model1, {returnIndex: true}), 5); - equal(col2.search(model2, {returnIndex: true}), 0); var col3 = new Backbone.Collection(models, { comparator: function (m1, m2) { return m2.get('a') - m1.get('a'); } }); - equal(col3.search(model1), model1); - equal(col3.search(model2), model2); + equal(col3.at(col3.search(model1)), model1); + equal(col3.at(col3.search(model2)), model2); }); test("where and findWhere", 16, function() { From 667d3804850c276fe8d48be0283e033c1e31ccff Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Sat, 30 Aug 2014 17:56:02 +0100 Subject: [PATCH 07/11] prevent setting both `getMin` and `getMax` --- backbone.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backbone.js b/backbone.js index ae146cfb6..6f4464bb9 100644 --- a/backbone.js +++ b/backbone.js @@ -830,9 +830,11 @@ // Perform a binary search for a model in a sorted collection. search: function (toFind, options) { - if (!this.comparator) throw new Error('Cannot search an unsorted Collection'); options || (options = {}); + if (!this.comparator) throw new Error('Cannot search an unsorted Collection'); + if (options.getMin && options.getMax) throw new Error('Cannot set both getMin and getMax'); + // Create `compare` function for searching. var compare; if (_.isFunction(toFind)) { From be8e07cbf6750eee3fe2aadc0d486f8f52dfdfe9 Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Sat, 3 Jan 2015 20:58:54 +0000 Subject: [PATCH 08/11] test _ to 1.7.0, use _.comparator, getMax bool now only have `getMax` boolean, if this is not truthy then the min is fetched bump test underscore to 1.7.0 (unreleased) --- backbone.js | 21 +- test/collection.js | 4 +- test/vendor/underscore.js | 1208 +++++++++++++++++++++---------------- 3 files changed, 710 insertions(+), 523 deletions(-) diff --git a/backbone.js b/backbone.js index 6f4464bb9..703307417 100644 --- a/backbone.js +++ b/backbone.js @@ -829,11 +829,8 @@ }, // Perform a binary search for a model in a sorted collection. - search: function (toFind, options) { - options || (options = {}); - + search: function (toFind, getMax) { if (!this.comparator) throw new Error('Cannot search an unsorted Collection'); - if (options.getMin && options.getMax) throw new Error('Cannot set both getMin and getMax'); // Create `compare` function for searching. var compare; @@ -846,16 +843,14 @@ compare = _.bind(this.comparator, this, toFind); } else { compare = _.bind(function (valResult, model) { - var modelResult = this.comparator(model); - return valResult === modelResult ? 0 : (valResult > modelResult ? 1 : -1); + return _.comparator(valResult, this.comparator(model)); }, this, this.comparator(toFind)); } } else { // `comparator` is a string indicating the model property used to sort // `toFind` is the value sought. compare = _.bind(function (model) { - var modelValue = model.get(this.comparator); - return toFind === modelValue ? 0 : (toFind > modelValue ? 1 : -1); + return _.comparator(toFind, model.get(this.comparator)); }, this); } @@ -868,9 +863,9 @@ min = index + 1; } else if (relValue < 0) { max = index - 1; - } else if (options.getMax && index < max && compare(this.at(index + 1)) === 0) { + } else if (getMax && index < max && compare(this.at(index + 1)) === 0) { min = index + 1; - } else if (options.getMin && index > min && compare(this.at(index - 1)) === 0) { + } else if (!getMax && index > min && compare(this.at(index - 1)) === 0) { max = index - 1; } else { found = true; @@ -893,10 +888,10 @@ return true; }); } else if (first) { - return this.at(this.search(attrs[this.comparator], {getMin: true})); + return this.at(this.search(attrs[this.comparator])); } else { - var minIndex = this.search(attrs[this.comparator], {getMin: true}); - var maxIndex = this.search(attrs[this.comparator], {getMax: true}); + var minIndex = this.search(attrs[this.comparator]); + var maxIndex = this.search(attrs[this.comparator], true); return minIndex == null ? [] : this.models.slice(minIndex, maxIndex + 1); } }, diff --git a/test/collection.js b/test/collection.js index 42fc31638..31395b95e 100644 --- a/test/collection.js +++ b/test/collection.js @@ -558,8 +558,8 @@ }); equal(coll.at(coll.search(1)).get('a'), 1); - equal(coll.search(1, {getMin: true}), 1); - equal(coll.search(1, {getMax: true}), 3); + equal(coll.search(1), 1); + equal(coll.search(1, true), 3); ok(coll.search(4) === -1); equal(coll.at(coll.search(function (model) { diff --git a/test/vendor/underscore.js b/test/vendor/underscore.js index 9a4cabecf..62bcf9006 100644 --- a/test/vendor/underscore.js +++ b/test/vendor/underscore.js @@ -1,4 +1,4 @@ -// Underscore.js 1.6.0 +// Underscore.js 1.7.0 // http://underscorejs.org // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. @@ -14,9 +14,6 @@ // 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; @@ -24,25 +21,19 @@ var push = ArrayProto.push, slice = ArrayProto.slice, - concat = ArrayProto.concat, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, nativeIsArray = Array.isArray, nativeKeys = Object.keys, - nativeBind = FuncProto.bind; + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Reusable constructor function for prototype setting. + var Ctor = function(){}; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { @@ -53,8 +44,7 @@ // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. + // the browser, add `_` as a global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; @@ -65,161 +55,235 @@ } // Current version. - _.VERSION = '1.6.0'; + _.VERSION = '1.7.0'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // A mostly-internal function to generate callbacks that can be applied + // to each element in a collection, returning the desired result — either + // identity, an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matches(value); + return _.property(value); + }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 0; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; // 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) { + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { if (obj == null) return obj; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, length = obj.length; i < length; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; + iteratee = optimizeCb(iteratee, context); + var i, length = obj.length; + if (length === +length) { + for (i = 0; i < length; i++) { + iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); - for (var i = 0, length = keys.length; i < length; i++) { - if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + if (obj == null) return []; + iteratee = cb(iteratee, context); + var keys = obj.length !== +obj.length && _.keys(obj), + length = (keys || obj).length, + results = Array(length), + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } 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) { - var initial = arguments.length > 2; + // or `foldl`. + _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) { if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + iteratee = optimizeCb(iteratee, context, 4); + var keys = obj.length !== +obj.length && _.keys(obj), + length = (keys || obj).length, + index = 0, currentKey; + if (arguments.length < 3) { + memo = obj[keys ? keys[index++] : index++]; + } + for (; index < length; index++) { + currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - 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) { - var initial = arguments.length > 2; + _.reduceRight = _.foldr = function(obj, iteratee, memo, context) { if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + iteratee = optimizeCb(iteratee, context, 4); + var keys = obj.length !== + obj.length && _.keys(obj), + index = (keys || obj).length, + currentKey; + if (arguments.length < 3) { + memo = obj[keys ? keys[--index] : --index]; } - var length = obj.length; - if (length !== +length) { - var keys = _.keys(obj); - length = keys.length; + while (index-- > 0) { + currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); } - each(obj, function(value, index, list) { - index = keys ? keys[--length] : --length; - if (!initial) { - memo = obj[index]; - initial = true; + return memo; + }; + + // **Transform** is an alternative to reduce that transforms `obj` to a new + // `accumulator` object. + _.transform = function(obj, iteratee, accumulator, context) { + if (accumulator == null) { + if (_.isArray(obj)) { + accumulator = []; + } else if (_.isObject(obj)) { + var Ctor = obj.constructor; + accumulator = baseCreate(typeof Ctor == 'function' && Ctor.prototype); } else { - memo = iterator.call(context, memo, obj[index], index, list); + accumulator = {}; } - }); - if (!initial) throw new TypeError(reduceError); - return memo; + } + if (obj == null) return accumulator; + iteratee = optimizeCb(iteratee, context, 4); + var keys = obj.length !== +obj.length && _.keys(obj), + length = (keys || obj).length, + index, currentKey; + for (index = 0; index < length; index++) { + currentKey = keys ? keys[index] : index; + if (iteratee(accumulator, obj[currentKey], currentKey, obj) === false) break; + } + return accumulator; }; // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { - var result; - any(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; + var key; + if (obj.length === +obj.length) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); - each(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) results.push(value); + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - return _.filter(obj, function(value, index, list) { - return !predicate.call(context, value, index, list); - }, context); + return _.filter(obj, _.negate(cb(predicate)), 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, predicate, context) { - predicate || (predicate = _.identity); - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); - each(obj, function(value, index, list) { - if (!(result = result && predicate.call(context, value, index, list))) return breaker; - }); - return !!result; + if (obj == null) return true; + predicate = cb(predicate, context); + var keys = obj.length !== +obj.length && _.keys(obj), + length = (keys || obj).length, + index, currentKey; + for (index = 0; index < length; index++) { + currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; }; // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. - var any = _.some = _.any = function(obj, predicate, context) { - predicate || (predicate = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); - each(obj, function(value, index, list) { - if (result || (result = predicate.call(context, value, index, list))) return breaker; - }); - return !!result; + _.some = _.any = function(obj, predicate, context) { + if (obj == null) return false; + predicate = cb(predicate, context); + var keys = obj.length !== +obj.length && _.keys(obj), + length = (keys || obj).length, + index, currentKey; + for (index = 0; index < length; index++) { + currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; }; // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, target, fromIndex) { if (obj == null) return false; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - return any(obj, function(value) { - return value === target; - }); + if (obj.length !== +obj.length) obj = _.values(obj); + return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. @@ -248,51 +312,67 @@ return _.find(obj, _.matches(attrs)); }; - // Return the maximum element or (element-based computation). - // Can't optimize arrays of integers longer than 65,535 elements. - // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.max.apply(Math, obj); - } - var result = -Infinity, lastComputed = -Infinity; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - if (computed > lastComputed) { - result = value; - lastComputed = computed; + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = obj.length === +obj.length ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } } - }); + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } return result; }; // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.min.apply(Math, obj); - } - var result = Infinity, lastComputed = Infinity; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - if (computed < lastComputed) { - result = value; - lastComputed = computed; + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = obj.length === +obj.length ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } } - }); + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } return result; }; - // Shuffle an array, using the modern version of the + // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { - var rand; - var index = 0; - var shuffled = []; - each(obj, function(value) { - rand = _.random(index++); - shuffled[index - 1] = shuffled[rand]; - shuffled[rand] = value; - }); + var set = obj && obj.length === +obj.length ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } return shuffled; }; @@ -307,41 +387,28 @@ return _.shuffle(obj).slice(0, Math.max(0, n)); }; - // An internal function to generate lookup iterators. - var lookupIterator = function(value) { - if (value == null) return _.identity; - if (_.isFunction(value)) return value; - return _.property(value); - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - iterator = lookupIterator(iterator); + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, - criteria: iterator.call(context, value, index, list) + criteria: iteratee(value, index, list) }; }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; - if (a !== b) { - if (a > b || a === void 0) return 1; - if (a < b || b === void 0) return -1; - } - return left.index - right.index; + return _.comparator(left.criteria, right.criteria) || left.index - right.index; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(behavior) { - return function(obj, iterator, context) { + return function(obj, iteratee, context) { var result = {}; - iterator = lookupIterator(iterator); - each(obj, function(value, index) { - var key = iterator.call(context, value, index, obj); - behavior(result, key, value); + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); }); return result; }; @@ -349,36 +416,23 @@ // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. - _.groupBy = group(function(result, key, value) { - _.has(result, key) ? result[key].push(value) : result[key] = [value]; + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. - _.indexBy = group(function(result, key, value) { + _.indexBy = group(function(result, value, key) { result[key] = value; }); // 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 = group(function(result, key) { - _.has(result, key) ? result[key]++ : result[key] = 1; + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; }); - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator, context) { - iterator = lookupIterator(iterator); - var value = iterator.call(context, obj); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >>> 1; - iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; - } - return low; - }; - // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; @@ -390,7 +444,18 @@ // 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; + return obj.length === +obj.length ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; }; // Array Functions @@ -401,9 +466,8 @@ // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; - if ((n == null) || guard) return array[0]; - if (n < 0) return []; - return slice.call(array, 0, n); + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on @@ -411,15 +475,15 @@ // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + return slice.call(array, 0, Math.max(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 array[array.length - 1]; - return slice.call(array, Math.max(array.length - n, 0)); + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. @@ -427,7 +491,7 @@ // the rest N values in the array. The **guard** // check allows it to work with `_.map`. _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, (n == null) || guard ? 1 : n); + return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. @@ -436,23 +500,28 @@ }; // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, output) { - if (shallow && _.every(input, _.isArray)) { - return concat.apply(output, input); - } - each(input, function(value) { - if (_.isArray(value) || _.isArguments(value)) { - shallow ? push.apply(output, value) : flatten(value, shallow, output); - } else { - output.push(value); + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0, value; + for (var i = startIndex || 0, length = input && input.length; i < length; i++) { + value = input[i]; + if (value && value.length >= 0 && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; } - }); + } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { - return flatten(array, shallow, []); + return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). @@ -460,72 +529,87 @@ return _.difference(array, slice.call(arguments, 1)); }; - // Split an array into two arrays: one whose elements all satisfy the given - // predicate, and one whose elements all do not satisfy the predicate. - _.partition = function(array, predicate) { - var pass = [], fail = []; - each(array, function(elem) { - (predicate(elem) ? pass : fail).push(elem); - }); - return [pass, fail]; - }; - // 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; + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (array == null) return []; + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; isSorted = false; } - var initial = iterator ? _.map(array, iterator, context) : array; - var results = []; + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; var seen = []; - each(initial, function(value, index) { - if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { - seen.push(value); - results.push(array[index]); + for (var i = 0, length = array.length; i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); } - }); - return results; + } + return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(_.flatten(arguments, true)); + return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.contains(other, item); - }); - }); + if (array == null) return []; + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = array.length; i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.contains(rest, value); }); + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. - _.zip = function() { - var length = _.max(_.pluck(arguments, 'length').concat(0)); - var results = new Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(arguments, '' + i); + _.zip = function(array) { + if (array == null) return []; + var length = _.max(arguments, 'length').length; + var results = Array(length); + while (length-- > 0) { + results[length] = _.pluck(arguments, length); } return results; }; + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + return _.zip.apply(null, array); + }; + // 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. @@ -542,40 +626,54 @@ return result; }; - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, length = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } + var i = 0, length = array && array.length; + if (typeof isSorted == 'number') { + i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; + } else if (isSorted && length) { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); for (; i < length; i++) if (array[i] === item) return i; return -1; }; - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var hasIndex = from != null; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { - return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + var idx = array ? array.length : 0; + if (typeof from == 'number') { + idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); } - var i = (hasIndex ? from : array.length); - while (i--) if (array[i] === item) return i; + while (--idx >= 0) if (array[idx] === item) return idx; return -1; }; + // Returns the first index on an array-like that passes a predicate test + _.findIndex = function(array, predicate, context) { + predicate = cb(predicate, context); + var length = array != null ? array.length : 0; + for (var i = 0; i < length; i++) { + if (predicate(array[i], i, array)) return i; + } + return -1; + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = array.length; + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (_.comparator(iteratee(array[mid]), value) < 0) low = mid + 1; else high = mid; + } + return low; + }; + // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). @@ -584,15 +682,13 @@ stop = start || 0; start = 0; } - step = arguments[2] || 1; + step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(length); + var range = Array(length); - while(idx < length) { - range[idx++] = start; - start += step; + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; } return range; @@ -601,25 +697,25 @@ // Function (ahem) Functions // ------------------ - // Reusable constructor function for prototype setting. - var ctor = function(){}; + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { - var args, bound; if (nativeBind && func.bind === 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))); - 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; + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + return function bound() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; }; @@ -628,14 +724,14 @@ // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); - return function() { + return function bound() { var position = 0; var args = boundArgs.slice(); for (var i = 0, length = args.length; i < length; i++) { if (args[i] === _) args[i] = arguments[position++]; } while (position < arguments.length) args.push(arguments[position++]); - return func.apply(this, args); + return executeBound(func, bound, this, this, args); }; }; @@ -643,34 +739,39 @@ // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length === 0) throw new Error('bindAll must be passed function names'); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; }; + memoize.cache = {}; + return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; + _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run @@ -681,12 +782,12 @@ var context, args, result; var timeout = null; var previous = 0; - options || (options = {}); + if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; }; return function() { var now = _.now(); @@ -694,12 +795,14 @@ var remaining = wait - (now - previous); context = this; args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } previous = now; result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } @@ -716,13 +819,14 @@ var later = function() { var last = _.now() - timestamp; - if (last < wait) { + + if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; } } }; @@ -732,9 +836,7 @@ args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; - if (!timeout) { - timeout = setTimeout(later, wait); - } + if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; @@ -744,19 +846,6 @@ }; }; - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - memo = func.apply(this, arguments); - func = null; - return memo; - }; - }; - // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. @@ -764,20 +853,27 @@ return _.partial(wrapper, func); }; + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { - var funcs = arguments; + var args = arguments; + var start = args.length - 1; return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; }; }; - // Returns a function that will only be executed after being called N times. + // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { @@ -786,16 +882,62 @@ }; }; + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + // Object Functions // ---------------- - // Retrieve the names of an object's properties. + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['constructor', 'valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var proto = typeof obj.constructor === 'function' ? FuncProto : ObjProto; + + while (nonEnumIdx--) { + var prop = nonEnumerableProps[nonEnumIdx]; + if (prop === 'constructor' ? _.has(obj, prop) : prop in obj && + obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.keysIn = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; @@ -803,7 +945,7 @@ _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; - var values = new Array(length); + var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } @@ -814,7 +956,7 @@ _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; - var pairs = new Array(length); + var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } @@ -842,49 +984,77 @@ }; // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - } - }); - return obj; + _.extend = createAssigner(_.keysIn); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } }; // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - each(keys, function(key) { - if (key in obj) copy[key] = obj[key]; - }); - return copy; + _.pick = function(obj, iteratee, context) { + var result = {}, key; + if (obj == null) return result; + if (_.isFunction(iteratee)) { + iteratee = optimizeCb(iteratee, context); + for (key in obj) { + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + } else { + var keys = flatten(arguments, false, false, 1); + obj = new Object(obj); + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (key in obj) result[key] = obj[key]; + } + } + return result; }; // Return a copy of the object without the blacklisted properties. - _.omit = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - for (var key in obj) { - if (!_.contains(keys, key)) copy[key] = obj[key]; + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; } - return copy; + return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; - } + if (!_.isObject(obj)) return obj; + for (var i = 1, length = arguments.length; i < length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (obj[prop] === void 0) obj[prop] = source[prop]; } - }); + } return obj; }; + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + _.create = function(prototype, props) { + var result = baseCreate(prototype); + if (props) _.assign(result, props); + return result; + }; + // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; @@ -903,7 +1073,7 @@ var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a == 1 / b; + if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. @@ -911,84 +1081,80 @@ if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); - if (className != toString.call(b)) return false; + if (className !== toString.call(b)) return false; switch (className) { - // Strings, numbers, dates, and booleans are compared by value. + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return a == String(b); + return '' + a === '' + b; case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } } - if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. - if (aStack[length] == a) return bStack[length] == b; - } - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor)) - && ('constructor' in a && 'constructor' in b)) { - return false; + if (aStack[length] === a) return bStack[length] === b; } + // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); - var size = 0, result = true; + // Recursively compare objects and arrays. - if (className == '[object Array]') { + if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); - return result; + return true; }; // Perform a deep comparison to check if two objects are equal. @@ -1000,7 +1166,7 @@ // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0; for (var key in obj) if (_.has(obj, key)) return false; return true; }; @@ -1013,33 +1179,35 @@ // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; + return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { - return obj === Object(obj); + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; }; - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { - return toString.call(obj) == '[object ' + name + ']'; + return toString.call(obj) === '[object ' + name + ']'; }; }); - // Define a fallback version of the method in browsers (ahem, IE), where + // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); + return _.has(obj, 'callee'); }; } - // Optimize `isFunction` if appropriate. - if (typeof (/./) !== 'function') { + // Optimize `isFunction` if appropriate. Work around an IE 11 bug (#1621). + // Work around a Safari 8 bug (#1929) + if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { - return typeof obj === 'function'; + return typeof obj == 'function' || false; }; } @@ -1050,12 +1218,12 @@ // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { - return _.isNumber(obj) && obj != +obj; + return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? @@ -1071,7 +1239,7 @@ // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); + return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions @@ -1084,39 +1252,64 @@ return this; }; - // Keep the identity function around for default iterators. + // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; + // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { - return function () { + return function() { return value; }; }; + _.noop = function(){}; + _.property = function(key) { return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Generates a function for a given object that returns a given property (including those of ancestors) + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { return obj[key]; }; }; // Returns a predicate for checking whether an object has a given set of `key:value` pairs. _.matches = function(attrs) { + var pairs = _.pairs(attrs), length = pairs.length; return function(obj) { - if (obj === attrs) return true; //avoid comparing an object to itself. - for (var key in attrs) { - if (attrs[key] !== obj[key]) - return false; + if (obj == null) return !length; + obj = new Object(obj); + for (var i = 0; i < length; i++) { + var pair = pairs[i], key = pair[0]; + if (pair[1] !== obj[key] || !(key in obj)) return false; } return true; + }; + }; + + // Default internal comparator for determining whether a is greater (1), + // equal (0) or less than (-1) some object b + _.comparator = function(a, b) { + if (a === b) return 0; + var isAComparable = a >= a, isBComparable = b >= b; + if (isAComparable || isBComparable) { + if (isAComparable && !isBComparable) return -1; + if (isBComparable && !isAComparable) return 1; } + return a > b ? 1 : (b > a) ? -1 : 0; }; // Run a function **n** times. - _.times = function(n, iterator, context) { + _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); - for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; @@ -1130,56 +1323,48 @@ }; // A (possibly faster) way to get the current timestamp as an integer. - _.now = Date.now || function() { return new Date().getTime(); }; - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - } + _.now = Date.now || function() { + return new Date().getTime(); }; - entityMap.unescape = _.invert(entityMap.escape); - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' }; + var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. - _.each(['escape', 'unescape'], function(method) { - _[method] = function(string) { - if (string == null) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; }; - }); + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. - _.result = function(object, property) { - if (object == null) return void 0; - var value = object[property]; + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } return _.isFunction(value) ? value.call(object) : value; }; - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - each(_.functions(obj), function(name) { - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; @@ -1208,22 +1393,26 @@ '\\': '\\', '\r': 'r', '\n': 'n', - '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ + var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source @@ -1233,19 +1422,18 @@ var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { + } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { + } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } - index = offset + match.length; + + // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; @@ -1255,29 +1443,31 @@ source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; + source + 'return __p;\n'; try { - render = new Function(settings.variable || 'obj', '_', source); + var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } - if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; - // Add a "chain" function, which will delegate to the wrapper. + // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { - return _(obj).chain(); + var instance = _(obj); + instance._chain = true; + return instance; }; // OOP @@ -1287,46 +1477,48 @@ // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); - if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { + _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); + return result(this, method.apply(this._wrapped, arguments)); }; }); - _.extend(_.prototype, { - - // Start chaining a wrapped Underscore object. - chain: function() { - this._chain = true; - return this; - }, - - // Extracts the result from a wrapped and chained object. - value: function() { - return this._wrapped; - } - - }); + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general @@ -1340,4 +1532,4 @@ return _; }); } -}).call(this); +}.call(this)); \ No newline at end of file From 3aca1201a4ca190c228ba40ff530d479c3200206 Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Sat, 3 Jan 2015 21:11:43 +0000 Subject: [PATCH 09/11] use _.matches in `where` updated code to match changes to backbone.js --- backbone.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/backbone.js b/backbone.js index 703307417..2a0d1f1fc 100644 --- a/backbone.js +++ b/backbone.js @@ -878,14 +878,10 @@ // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { - if (_.isEmpty(attrs)) { - return first ? void 0 : []; - } else if (!_.isEqual(_.keys(attrs), [this.comparator])) { + if (_.isEmpty(attrs) || !_.isEqual(_.keys(attrs), [this.comparator])) { + var matches = _.matches(attrs); return this[first ? 'find' : 'filter'](function(model) { - for (var key in attrs) { - if (attrs[key] !== model.get(key)) return false; - } - return true; + return matches(model.attributes); }); } else if (first) { return this.at(this.search(attrs[this.comparator])); From 60038bbc6a953fcac1cc8355518615b79e24adac Mon Sep 17 00:00:00 2001 From: Joshua Bambrick Date: Fri, 17 Apr 2015 16:17:40 +0100 Subject: [PATCH 10/11] use local comparator, reset underscore.js version --- backbone.js | 16 +- test/vendor/underscore.js | 1208 ++++++++++++++++--------------------- 2 files changed, 522 insertions(+), 702 deletions(-) diff --git a/backbone.js b/backbone.js index 2a0d1f1fc..668a0db66 100644 --- a/backbone.js +++ b/backbone.js @@ -629,6 +629,18 @@ var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, remove: false}; + // Compare two values, in a `sort`-consistent way + var compareVals = function(a, b) { + if (a === b) return 0; + var isAComparable = a >= a, isBComparable = b >= b; + if (isAComparable || isBComparable) { + if (isAComparable && !isBComparable) return -1; + if (isBComparable && !isAComparable) return 1; + } + return a > b ? 1 : (b > a) ? -1 : 0; + }; + + // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { @@ -843,14 +855,14 @@ compare = _.bind(this.comparator, this, toFind); } else { compare = _.bind(function (valResult, model) { - return _.comparator(valResult, this.comparator(model)); + return compareVals(valResult, this.comparator(model)); }, this, this.comparator(toFind)); } } else { // `comparator` is a string indicating the model property used to sort // `toFind` is the value sought. compare = _.bind(function (model) { - return _.comparator(toFind, model.get(this.comparator)); + return compareVals(toFind, model.get(this.comparator)); }, this); } diff --git a/test/vendor/underscore.js b/test/vendor/underscore.js index 62bcf9006..00d3deb92 100644 --- a/test/vendor/underscore.js +++ b/test/vendor/underscore.js @@ -1,4 +1,4 @@ -// Underscore.js 1.7.0 +// Underscore.js 1.6.0 // http://underscorejs.org // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. @@ -14,6 +14,9 @@ // 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; @@ -21,19 +24,25 @@ var push = ArrayProto.push, slice = ArrayProto.slice, + concat = ArrayProto.concat, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, nativeIsArray = Array.isArray, nativeKeys = Object.keys, - nativeBind = FuncProto.bind, - nativeCreate = Object.create; - - // Reusable constructor function for prototype setting. - var Ctor = function(){}; + nativeBind = FuncProto.bind; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { @@ -44,7 +53,8 @@ // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object. + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; @@ -55,235 +65,161 @@ } // Current version. - _.VERSION = '1.7.0'; - - // Internal function that returns an efficient (for current engines) version - // of the passed-in callback, to be repeatedly applied in other Underscore - // functions. - var optimizeCb = function(func, context, argCount) { - if (context === void 0) return func; - switch (argCount == null ? 3 : argCount) { - case 1: return function(value) { - return func.call(context, value); - }; - case 2: return function(value, other) { - return func.call(context, value, other); - }; - case 3: return function(value, index, collection) { - return func.call(context, value, index, collection); - }; - case 4: return function(accumulator, value, index, collection) { - return func.call(context, accumulator, value, index, collection); - }; - } - return function() { - return func.apply(context, arguments); - }; - }; - - // A mostly-internal function to generate callbacks that can be applied - // to each element in a collection, returning the desired result — either - // identity, an arbitrary callback, a property matcher, or a property accessor. - var cb = function(value, context, argCount) { - if (value == null) return _.identity; - if (_.isFunction(value)) return optimizeCb(value, context, argCount); - if (_.isObject(value)) return _.matches(value); - return _.property(value); - }; - _.iteratee = function(value, context) { - return cb(value, context, Infinity); - }; - - // An internal function for creating assigner functions. - var createAssigner = function(keysFunc) { - return function(obj) { - var length = arguments.length; - if (length < 2 || obj == null) return obj; - for (var index = 0; index < length; index++) { - var source = arguments[index], - keys = keysFunc(source), - l = keys.length; - for (var i = 0; i < l; i++) { - var key = keys[i]; - obj[key] = source[key]; - } - } - return obj; - }; - }; - - // An internal function for creating a new object that inherits from another. - var baseCreate = function(prototype) { - if (!_.isObject(prototype)) return {}; - if (nativeCreate) return nativeCreate(prototype); - Ctor.prototype = prototype; - var result = new Ctor; - Ctor.prototype = null; - return result; - }; + _.VERSION = '1.6.0'; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. - // Handles raw objects in addition to array-likes. Treats all - // sparse array-likes as if they were dense. - _.each = _.forEach = function(obj, iteratee, context) { + // 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 obj; - iteratee = optimizeCb(iteratee, context); - var i, length = obj.length; - if (length === +length) { - for (i = 0; i < length; i++) { - iteratee(obj[i], i, obj); + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, length = obj.length; i < length; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { var keys = _.keys(obj); - for (i = 0, length = keys.length; i < length; i++) { - iteratee(obj[keys[i]], keys[i], obj); + for (var i = 0, length = keys.length; i < length; i++) { + if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; } } return obj; }; - // Return the results of applying the iteratee to each element. - _.map = _.collect = function(obj, iteratee, context) { - if (obj == null) return []; - iteratee = cb(iteratee, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - results = Array(length), - currentKey; - for (var index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - results[index] = iteratee(obj[currentKey], currentKey, obj); - } + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); 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`. - _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) { + // 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 = []; - iteratee = optimizeCb(iteratee, context, 4); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index = 0, currentKey; - if (arguments.length < 3) { - memo = obj[keys ? keys[index++] : index++]; - } - for (; index < length; index++) { - currentKey = keys ? keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); return memo; }; // The right-associative version of reduce, also known as `foldr`. - _.reduceRight = _.foldr = function(obj, iteratee, memo, context) { + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; if (obj == null) obj = []; - iteratee = optimizeCb(iteratee, context, 4); - var keys = obj.length !== + obj.length && _.keys(obj), - index = (keys || obj).length, - currentKey; - if (arguments.length < 3) { - memo = obj[keys ? keys[--index] : --index]; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } - while (index-- > 0) { - currentKey = keys ? keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; } - return memo; - }; - - // **Transform** is an alternative to reduce that transforms `obj` to a new - // `accumulator` object. - _.transform = function(obj, iteratee, accumulator, context) { - if (accumulator == null) { - if (_.isArray(obj)) { - accumulator = []; - } else if (_.isObject(obj)) { - var Ctor = obj.constructor; - accumulator = baseCreate(typeof Ctor == 'function' && Ctor.prototype); + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; } else { - accumulator = {}; + memo = iterator.call(context, memo, obj[index], index, list); } - } - if (obj == null) return accumulator; - iteratee = optimizeCb(iteratee, context, 4); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - if (iteratee(accumulator, obj[currentKey], currentKey, obj) === false) break; - } - return accumulator; + }); + if (!initial) throw new TypeError(reduceError); + return memo; }; // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { - var key; - if (obj.length === +obj.length) { - key = _.findIndex(obj, predicate, context); - } else { - key = _.findKey(obj, predicate, context); - } - if (key !== void 0 && key !== -1) return obj[key]; + var result; + any(obj, function(value, index, list) { + if (predicate.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; }; // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; if (obj == null) return results; - predicate = cb(predicate, context); - _.each(obj, function(value, index, list) { - if (predicate(value, index, list)) results.push(value); + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); + each(obj, function(value, index, list) { + if (predicate.call(context, value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - return _.filter(obj, _.negate(cb(predicate)), context); + return _.filter(obj, function(value, index, list) { + return !predicate.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, predicate, context) { - if (obj == null) return true; - predicate = cb(predicate, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - if (!predicate(obj[currentKey], currentKey, obj)) return false; - } - return true; + predicate || (predicate = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); + each(obj, function(value, index, list) { + if (!(result = result && predicate.call(context, value, index, list))) return breaker; + }); + return !!result; }; // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. - _.some = _.any = function(obj, predicate, context) { - if (obj == null) return false; - predicate = cb(predicate, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; - if (predicate(obj[currentKey], currentKey, obj)) return true; - } - return false; + var any = _.some = _.any = function(obj, predicate, context) { + predicate || (predicate = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); + each(obj, function(value, index, list) { + if (result || (result = predicate.call(context, value, index, list))) return breaker; + }); + return !!result; }; // Determine if the array or object contains a given value (using `===`). - // Aliased as `includes` and `include`. - _.contains = _.includes = _.include = function(obj, target, fromIndex) { + // Aliased as `include`. + _.contains = _.include = function(obj, target) { if (obj == null) return false; - if (obj.length !== +obj.length) obj = _.values(obj); - return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); }; // Invoke a method (with arguments) on every item in a collection. @@ -312,67 +248,51 @@ return _.find(obj, _.matches(attrs)); }; - // Return the maximum element (or element-based computation). - _.max = function(obj, iteratee, context) { - var result = -Infinity, lastComputed = -Infinity, - value, computed; - if (iteratee == null && obj != null) { - obj = obj.length === +obj.length ? obj : _.values(obj); - for (var i = 0, length = obj.length; i < length; i++) { - value = obj[i]; - if (value > result) { - result = value; - } - } - } else { - iteratee = cb(iteratee, context); - _.each(obj, function(value, index, list) { - computed = iteratee(value, index, list); - if (computed > lastComputed || computed === -Infinity && result === -Infinity) { - result = value; - lastComputed = computed; - } - }); + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); } + var result = -Infinity, lastComputed = -Infinity; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + if (computed > lastComputed) { + result = value; + lastComputed = computed; + } + }); return result; }; // Return the minimum element (or element-based computation). - _.min = function(obj, iteratee, context) { - var result = Infinity, lastComputed = Infinity, - value, computed; - if (iteratee == null && obj != null) { - obj = obj.length === +obj.length ? obj : _.values(obj); - for (var i = 0, length = obj.length; i < length; i++) { - value = obj[i]; - if (value < result) { - result = value; - } - } - } else { - iteratee = cb(iteratee, context); - _.each(obj, function(value, index, list) { - computed = iteratee(value, index, list); - if (computed < lastComputed || computed === Infinity && result === Infinity) { - result = value; - lastComputed = computed; - } - }); + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); } + var result = Infinity, lastComputed = Infinity; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + if (computed < lastComputed) { + result = value; + lastComputed = computed; + } + }); return result; }; - // Shuffle a collection, using the modern version of the + // Shuffle an array, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { - var set = obj && obj.length === +obj.length ? obj : _.values(obj); - var length = set.length; - var shuffled = Array(length); - for (var index = 0, rand; index < length; index++) { - rand = _.random(0, index); - if (rand !== index) shuffled[index] = shuffled[rand]; - shuffled[rand] = set[index]; - } + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); return shuffled; }; @@ -387,28 +307,41 @@ return _.shuffle(obj).slice(0, Math.max(0, n)); }; - // Sort the object's values by a criterion produced by an iteratee. - _.sortBy = function(obj, iteratee, context) { - iteratee = cb(iteratee, context); + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + if (value == null) return _.identity; + if (_.isFunction(value)) return value; + return _.property(value); + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, iterator, context) { + iterator = lookupIterator(iterator); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, - criteria: iteratee(value, index, list) + criteria: iterator.call(context, value, index, list) }; }).sort(function(left, right) { - return _.comparator(left.criteria, right.criteria) || left.index - right.index; + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(behavior) { - return function(obj, iteratee, context) { + return function(obj, iterator, context) { var result = {}; - iteratee = cb(iteratee, context); - _.each(obj, function(value, index) { - var key = iteratee(value, index, obj); - behavior(result, value, key); + iterator = lookupIterator(iterator); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); }); return result; }; @@ -416,23 +349,36 @@ // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. - _.groupBy = group(function(result, value, key) { - if (_.has(result, key)) result[key].push(value); else result[key] = [value]; + _.groupBy = group(function(result, key, value) { + _.has(result, key) ? result[key].push(value) : result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. - _.indexBy = group(function(result, value, key) { + _.indexBy = group(function(result, key, value) { result[key] = value; }); // 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 = group(function(result, value, key) { - if (_.has(result, key)) result[key]++; else result[key] = 1; + _.countBy = group(function(result, key) { + _.has(result, key) ? result[key]++ : result[key] = 1; }); + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; @@ -444,18 +390,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; - }; - - // Split a collection into two arrays: one whose elements all satisfy the given - // predicate, and one whose elements all do not satisfy the predicate. - _.partition = function(obj, predicate, context) { - predicate = cb(predicate, context); - var pass = [], fail = []; - _.each(obj, function(value, key, obj) { - (predicate(value, key, obj) ? pass : fail).push(value); - }); - return [pass, fail]; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; }; // Array Functions @@ -466,8 +401,9 @@ // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; - if (n == null || guard) return array[0]; - return _.initial(array, array.length - n); + if ((n == null) || guard) return array[0]; + if (n < 0) return []; + return slice.call(array, 0, n); }; // Returns everything but the last entry of the array. Especially useful on @@ -475,15 +411,15 @@ // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. _.initial = function(array, n, guard) { - return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + 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 array[array.length - 1]; - return _.rest(array, Math.max(0, array.length - n)); + if ((n == null) || guard) return array[array.length - 1]; + return slice.call(array, Math.max(array.length - n, 0)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. @@ -491,7 +427,7 @@ // the rest N values in the array. The **guard** // check allows it to work with `_.map`. _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, n == null || guard ? 1 : n); + return slice.call(array, (n == null) || guard ? 1 : n); }; // Trim out all falsy values from an array. @@ -500,28 +436,23 @@ }; // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, strict, startIndex) { - var output = [], idx = 0, value; - for (var i = startIndex || 0, length = input && input.length; i < length; i++) { - value = input[i]; - if (value && value.length >= 0 && (_.isArray(value) || _.isArguments(value))) { - //flatten current level of array or arguments object - if (!shallow) value = flatten(value, shallow, strict); - var j = 0, len = value.length; - output.length += len; - while (j < len) { - output[idx++] = value[j++]; - } - } else if (!strict) { - output[idx++] = value; - } + var flatten = function(input, shallow, output) { + if (shallow && _.every(input, _.isArray)) { + return concat.apply(output, input); } + each(input, function(value) { + if (_.isArray(value) || _.isArguments(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { - return flatten(array, shallow, false); + return flatten(array, shallow, []); }; // Return a version of the array that does not contain the specified value(s). @@ -529,87 +460,72 @@ return _.difference(array, slice.call(arguments, 1)); }; + // Split an array into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(array, predicate) { + var pass = [], fail = []; + each(array, function(elem) { + (predicate(elem) ? pass : fail).push(elem); + }); + return [pass, fail]; + }; + // 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, iteratee, context) { - if (array == null) return []; - if (!_.isBoolean(isSorted)) { - context = iteratee; - iteratee = isSorted; + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; isSorted = false; } - if (iteratee != null) iteratee = cb(iteratee, context); - var result = []; + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; var seen = []; - for (var i = 0, length = array.length; i < length; i++) { - var value = array[i], - computed = iteratee ? iteratee(value, i, array) : value; - if (isSorted) { - if (!i || seen !== computed) result.push(value); - seen = computed; - } else if (iteratee) { - if (!_.contains(seen, computed)) { - seen.push(computed); - result.push(value); - } - } else if (!_.contains(result, value)) { - result.push(value); + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); } - } - return result; + }); + return results; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(flatten(arguments, true, true)); + return _.uniq(_.flatten(arguments, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { - if (array == null) return []; - var result = []; - var argsLength = arguments.length; - for (var i = 0, length = array.length; i < length; i++) { - var item = array[i]; - if (_.contains(result, item)) continue; - for (var j = 1; j < argsLength; j++) { - if (!_.contains(arguments[j], item)) break; - } - if (j === argsLength) result.push(item); - } - return result; + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.contains(other, item); + }); + }); }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = flatten(arguments, true, true, 1); - return _.filter(array, function(value){ - return !_.contains(rest, value); - }); + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. - _.zip = function(array) { - if (array == null) return []; - var length = _.max(arguments, 'length').length; - var results = Array(length); - while (length-- > 0) { - results[length] = _.pluck(arguments, length); + _.zip = function() { + var length = _.max(_.pluck(arguments, 'length').concat(0)); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(arguments, '' + i); } return results; }; - // Complement of _.zip. Unzip accepts an array of arrays and groups - // each array's elements on shared indices - _.unzip = function(array) { - return _.zip.apply(null, array); - }; - // 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. @@ -626,54 +542,40 @@ return result; }; - // Return the position of the first occurrence of an item in an array, - // or -1 if the item is not included in the array. + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { - var i = 0, length = array && array.length; - if (typeof isSorted == 'number') { - i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; - } else if (isSorted && length) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; + if (array == null) return -1; + var i = 0, length = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); for (; i < length; i++) if (array[i] === item) return i; return -1; }; + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item, from) { - var idx = array ? array.length : 0; - if (typeof from == 'number') { - idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); } - while (--idx >= 0) if (array[idx] === item) return idx; + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; return -1; }; - // Returns the first index on an array-like that passes a predicate test - _.findIndex = function(array, predicate, context) { - predicate = cb(predicate, context); - var length = array != null ? array.length : 0; - for (var i = 0; i < length; i++) { - if (predicate(array[i], i, array)) return i; - } - return -1; - }; - - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iteratee, context) { - iteratee = cb(iteratee, context, 1); - var value = iteratee(obj); - var low = 0, high = array.length; - while (low < high) { - var mid = Math.floor((low + high) / 2); - if (_.comparator(iteratee(array[mid]), value) < 0) low = mid + 1; else high = mid; - } - return low; - }; - // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). @@ -682,13 +584,15 @@ stop = start || 0; start = 0; } - step = step || 1; + step = arguments[2] || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); - var range = Array(length); + var idx = 0; + var range = new Array(length); - for (var idx = 0; idx < length; idx++, start += step) { - range[idx] = start; + while(idx < length) { + range[idx++] = start; + start += step; } return range; @@ -697,25 +601,25 @@ // Function (ahem) Functions // ------------------ - // Determines whether to execute a function as a constructor - // or a normal function with the provided arguments - var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { - if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); - var self = baseCreate(sourceFunc.prototype); - var result = sourceFunc.apply(self, args); - if (_.isObject(result)) return result; - return self; - }; + // Reusable constructor function for prototype setting. + var ctor = function(){}; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { + var args, bound; if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); - var args = slice.call(arguments, 2); - return function bound() { - return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); + 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))); + 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; }; }; @@ -724,14 +628,14 @@ // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); - return function bound() { + return function() { var position = 0; var args = boundArgs.slice(); for (var i = 0, length = args.length; i < length; i++) { if (args[i] === _) args[i] = arguments[position++]; } while (position < arguments.length) args.push(arguments[position++]); - return executeBound(func, bound, this, this, args); + return func.apply(this, args); }; }; @@ -739,39 +643,34 @@ // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { - var i, length = arguments.length, key; - if (length <= 1) throw new Error('bindAll must be passed function names'); - for (i = 1; i < length; i++) { - key = arguments[i]; - obj[key] = _.bind(obj[key], obj); - } + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) throw new Error('bindAll must be passed function names'); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { - var memoize = function(key) { - var cache = memoize.cache; - var address = '' + (hasher ? hasher.apply(this, arguments) : key); - if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); - return cache[address]; + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); }; - memoize.cache = {}; - return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); - return setTimeout(function(){ - return func.apply(null, args); - }, wait); + return setTimeout(function(){ return func.apply(null, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. - _.defer = _.partial(_.delay, _, 1); + _.defer = function(func) { + 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. Normally, the throttled function will run @@ -782,12 +681,12 @@ var context, args, result; var timeout = null; var previous = 0; - if (!options) options = {}; + options || (options = {}); var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); - if (!timeout) context = args = null; + context = args = null; }; return function() { var now = _.now(); @@ -795,14 +694,12 @@ var remaining = wait - (now - previous); context = this; args = arguments; - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; previous = now; result = func.apply(context, args); - if (!timeout) context = args = null; + context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } @@ -819,14 +716,13 @@ var later = function() { var last = _.now() - timestamp; - - if (last < wait && last >= 0) { + if (last < wait) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); - if (!timeout) context = args = null; + context = args = null; } } }; @@ -836,7 +732,9 @@ args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; - if (!timeout) timeout = setTimeout(later, wait); + if (!timeout) { + timeout = setTimeout(later, wait); + } if (callNow) { result = func.apply(context, args); context = args = null; @@ -846,6 +744,19 @@ }; }; + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. @@ -853,27 +764,20 @@ return _.partial(wrapper, func); }; - // Returns a negated version of the passed-in predicate. - _.negate = function(predicate) { - return function() { - return !predicate.apply(this, arguments); - }; - }; - // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { - var args = arguments; - var start = args.length - 1; + var funcs = arguments; return function() { - var i = start; - var result = args[start].apply(this, arguments); - while (i--) result = args[i].call(this, result); - return result; + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; }; }; - // Returns a function that will only be executed on and after the Nth call. + // Returns a function that will only be executed after being called N times. _.after = function(times, func) { return function() { if (--times < 1) { @@ -882,62 +786,16 @@ }; }; - // Returns a function that will only be executed up to (but not including) the Nth call. - _.before = function(times, func) { - var memo; - return function() { - if (--times > 0) { - memo = func.apply(this, arguments); - } - if (times <= 1) func = null; - return memo; - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = _.partial(_.before, 2); - // Object Functions // ---------------- - // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. - var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); - var nonEnumerableProps = ['constructor', 'valueOf', 'isPrototypeOf', 'toString', - 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; - - function collectNonEnumProps(obj, keys) { - var nonEnumIdx = nonEnumerableProps.length; - var proto = typeof obj.constructor === 'function' ? FuncProto : ObjProto; - - while (nonEnumIdx--) { - var prop = nonEnumerableProps[nonEnumIdx]; - if (prop === 'constructor' ? _.has(obj, prop) : prop in obj && - obj[prop] !== proto[prop] && !_.contains(keys, prop)) { - keys.push(prop); - } - } - } - - // Retrieve the names of an object's own properties. + // Retrieve the names of an object's properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); - // Ahem, IE < 9. - if (hasEnumBug) collectNonEnumProps(obj, keys); - return keys; - }; - - // Retrieve all the property names of an object. - _.keysIn = function(obj) { - if (!_.isObject(obj)) return []; - var keys = []; - for (var key in obj) keys.push(key); - // Ahem, IE < 9. - if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; @@ -945,7 +803,7 @@ _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; - var values = Array(length); + var values = new Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } @@ -956,7 +814,7 @@ _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; - var pairs = Array(length); + var pairs = new Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } @@ -984,77 +842,49 @@ }; // Extend a given object with all the properties in passed-in object(s). - _.extend = createAssigner(_.keysIn); - - // Assigns a given object with all the own properties in the passed-in object(s) - // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) - _.assign = createAssigner(_.keys); - - // Returns the first key on an object that passes a predicate test - _.findKey = function(obj, predicate, context) { - predicate = cb(predicate, context); - var keys = _.keys(obj), key; - for (var i = 0, length = keys.length; i < length; i++) { - key = keys[i]; - if (predicate(obj[key], key, obj)) return key; - } + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; }; // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj, iteratee, context) { - var result = {}, key; - if (obj == null) return result; - if (_.isFunction(iteratee)) { - iteratee = optimizeCb(iteratee, context); - for (key in obj) { - var value = obj[key]; - if (iteratee(value, key, obj)) result[key] = value; - } - } else { - var keys = flatten(arguments, false, false, 1); - obj = new Object(obj); - for (var i = 0, length = keys.length; i < length; i++) { - key = keys[i]; - if (key in obj) result[key] = obj[key]; - } - } - return result; + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; }; // Return a copy of the object without the blacklisted properties. - _.omit = function(obj, iteratee, context) { - if (_.isFunction(iteratee)) { - iteratee = _.negate(iteratee); - } else { - var keys = _.map(flatten(arguments, false, false, 1), String); - iteratee = function(value, key) { - return !_.contains(keys, key); - }; + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; } - return _.pick(obj, iteratee, context); + return copy; }; // Fill in a given object with default properties. _.defaults = function(obj) { - if (!_.isObject(obj)) return obj; - for (var i = 1, length = arguments.length; i < length; i++) { - var source = arguments[i]; - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] === void 0) obj[prop] = source[prop]; + } } - } + }); return obj; }; - // Creates an object that inherits from the given prototype object. - // If additional properties are provided then they will be added to the - // created object. - _.create = function(prototype, props) { - var result = baseCreate(prototype); - if (props) _.assign(result, props); - return result; - }; - // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; @@ -1073,7 +903,7 @@ var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a === 1 / b; + if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. @@ -1081,80 +911,84 @@ if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); - if (className !== toString.call(b)) return false; + if (className != toString.call(b)) return false; switch (className) { - // Strings, numbers, regular expressions, dates, and booleans are compared by value. - case '[object RegExp]': - // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return '' + a === '' + b; + return a == String(b); case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. - // Object(NaN) is equivalent to NaN - if (+a !== +a) return +b !== +b; - // An `egal` comparison is performed for other numeric values. - return +a === 0 ? 1 / +a === 1 / b : +a === +b; + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. - return +a === +b; - } - - var areArrays = className === '[object Array]'; - if (!areArrays) { - if (typeof a != 'object' || typeof b != 'object') return false; - - // Objects with different constructors are not equivalent, but `Object`s or `Array`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && - _.isFunction(bCtor) && bCtor instanceof bCtor) - && ('constructor' in a && 'constructor' in b)) { - return false; - } + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; } + if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. - if (aStack[length] === a) return bStack[length] === b; + if (aStack[length] == a) return bStack[length] == b; + } + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor)) + && ('constructor' in a && 'constructor' in b)) { + return false; } - // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); - + var size = 0, result = true; // Recursively compare objects and arrays. - if (areArrays) { + if (className == '[object Array]') { // Compare array lengths to determine if a deep comparison is necessary. - length = a.length; - if (length !== b.length) return false; - // Deep compare the contents, ignoring non-numeric properties. - while (length--) { - if (!eq(a[length], b[length], aStack, bStack)) return false; + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } } } else { // Deep compare objects. - var keys = _.keys(a), key; - length = keys.length; - // Ensure that both objects contain the same number of properties before comparing deep equality. - if (_.keys(b).length !== length) return false; - while (length--) { - // Deep compare each member - key = keys[length]; - if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); - return true; + return result; }; // Perform a deep comparison to check if two objects are equal. @@ -1166,7 +1000,7 @@ // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (_.has(obj, key)) return false; return true; }; @@ -1179,35 +1013,33 @@ // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; + return toString.call(obj) == '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { - var type = typeof obj; - return type === 'function' || type === 'object' && !!obj; + return obj === Object(obj); }; - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. - _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { _['is' + name] = function(obj) { - return toString.call(obj) === '[object ' + name + ']'; + return toString.call(obj) == '[object ' + name + ']'; }; }); - // Define a fallback version of the method in browsers (ahem, IE < 9), where + // Define a fallback version of the method in browsers (ahem, IE), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { - return _.has(obj, 'callee'); + return !!(obj && _.has(obj, 'callee')); }; } - // Optimize `isFunction` if appropriate. Work around an IE 11 bug (#1621). - // Work around a Safari 8 bug (#1929) - if (typeof /./ != 'function' && typeof Int8Array != 'object') { + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { _.isFunction = function(obj) { - return typeof obj == 'function' || false; + return typeof obj === 'function'; }; } @@ -1218,12 +1050,12 @@ // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { - return _.isNumber(obj) && obj !== +obj; + return _.isNumber(obj) && obj != +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; // Is a given value equal to null? @@ -1239,7 +1071,7 @@ // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { - return obj != null && hasOwnProperty.call(obj, key); + return hasOwnProperty.call(obj, key); }; // Utility Functions @@ -1252,64 +1084,39 @@ return this; }; - // Keep the identity function around for default iteratees. + // Keep the identity function around for default iterators. _.identity = function(value) { return value; }; - // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { - return function() { + return function () { return value; }; }; - _.noop = function(){}; - _.property = function(key) { return function(obj) { - return obj == null ? void 0 : obj[key]; - }; - }; - - // Generates a function for a given object that returns a given property (including those of ancestors) - _.propertyOf = function(obj) { - return obj == null ? function(){} : function(key) { return obj[key]; }; }; // Returns a predicate for checking whether an object has a given set of `key:value` pairs. _.matches = function(attrs) { - var pairs = _.pairs(attrs), length = pairs.length; return function(obj) { - if (obj == null) return !length; - obj = new Object(obj); - for (var i = 0; i < length; i++) { - var pair = pairs[i], key = pair[0]; - if (pair[1] !== obj[key] || !(key in obj)) return false; + if (obj === attrs) return true; //avoid comparing an object to itself. + for (var key in attrs) { + if (attrs[key] !== obj[key]) + return false; } return true; - }; - }; - - // Default internal comparator for determining whether a is greater (1), - // equal (0) or less than (-1) some object b - _.comparator = function(a, b) { - if (a === b) return 0; - var isAComparable = a >= a, isBComparable = b >= b; - if (isAComparable || isBComparable) { - if (isAComparable && !isBComparable) return -1; - if (isBComparable && !isAComparable) return 1; } - return a > b ? 1 : (b > a) ? -1 : 0; }; // Run a function **n** times. - _.times = function(n, iteratee, context) { + _.times = function(n, iterator, context) { var accum = Array(Math.max(0, n)); - iteratee = optimizeCb(iteratee, context, 1); - for (var i = 0; i < n; i++) accum[i] = iteratee(i); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); return accum; }; @@ -1323,48 +1130,56 @@ }; // A (possibly faster) way to get the current timestamp as an integer. - _.now = Date.now || function() { - return new Date().getTime(); + _.now = Date.now || function() { return new Date().getTime(); }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } }; + entityMap.unescape = _.invert(entityMap.escape); - // List of HTML entities for escaping. - var escapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '`': '`' + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') }; - var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. - var createEscaper = function(map) { - var escaper = function(match) { - return map[match]; - }; - // Regexes for identifying a key that needs to be escaped - var source = '(?:' + _.keys(map).join('|') + ')'; - var testRegexp = RegExp(source); - var replaceRegexp = RegExp(source, 'g'); - return function(string) { - string = string == null ? '' : '' + string; - return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); }; - }; - _.escape = createEscaper(escapeMap); - _.unescape = createEscaper(unescapeMap); + }); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. - _.result = function(object, property, fallback) { - var value = object == null ? void 0 : object[property]; - if (value === void 0) { - value = fallback; - } + _.result = function(object, property) { + if (object == null) return void 0; + var value = object[property]; return _.isFunction(value) ? value.call(object) : value; }; + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; @@ -1393,26 +1208,22 @@ '\\': '\\', '\r': 'r', '\n': 'n', + '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; - var escaper = /\\|'|\r|\n|\u2028|\u2029/g; - - var escapeChar = function(match) { - return '\\' + escapes[match]; - }; + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. - // NB: `oldSettings` only exists for backwards compatibility. - _.template = function(text, settings, oldSettings) { - if (!settings && oldSettings) settings = oldSettings; + _.template = function(text, data, settings) { + var render; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. - var matcher = RegExp([ + var matcher = new RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source @@ -1422,18 +1233,19 @@ var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset).replace(escaper, escapeChar); - index = offset + match.length; + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } else if (interpolate) { + } + if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } else if (evaluate) { + } + if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } - - // Adobe VMs need the match returned to produce the correct offest. + index = offset + match.length; return match; }); source += "';\n"; @@ -1443,31 +1255,29 @@ source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + - source + 'return __p;\n'; + source + "return __p;\n"; try { - var render = new Function(settings.variable || 'obj', '_', source); + render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } + if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; - // Provide the compiled source as a convenience for precompilation. - var argument = settings.variable || 'obj'; - template.source = 'function(' + argument + '){\n' + source + '}'; + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; return template; }; - // Add a "chain" function. Start chaining a wrapped Underscore object. + // Add a "chain" function, which will delegate to the wrapper. _.chain = function(obj) { - var instance = _(obj); - instance._chain = true; - return instance; + return _(obj).chain(); }; // OOP @@ -1477,48 +1287,46 @@ // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. - var result = function(instance, obj) { - return instance._chain ? _(obj).chain() : obj; - }; - - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - _.each(_.functions(obj), function(name) { - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result(this, func.apply(_, args)); - }; - }); + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. - _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); - if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; - return result(this, obj); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); }; }); // Add all accessor Array functions to the wrapper. - _.each(['concat', 'join', 'slice'], function(name) { + each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { - return result(this, method.apply(this._wrapped, arguments)); + return result.call(this, method.apply(this._wrapped, arguments)); }; }); - // Extracts the result from a wrapped and chained object. - _.prototype.value = function() { - return this._wrapped; - }; + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general @@ -1532,4 +1340,4 @@ return _; }); } -}.call(this)); \ No newline at end of file +}).call(this); \ No newline at end of file From 7c618edb3c1abda946eb67fc457b409f03c1cb8e Mon Sep 17 00:00:00 2001 From: Josh Bambrick Date: Thu, 23 Apr 2015 19:40:50 +0100 Subject: [PATCH 11/11] add new line to underscore.js to avoid having to update this file at all, adding the newline back in at the end --- test/vendor/underscore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/vendor/underscore.js b/test/vendor/underscore.js index 00d3deb92..9a4cabecf 100644 --- a/test/vendor/underscore.js +++ b/test/vendor/underscore.js @@ -1340,4 +1340,4 @@ return _; }); } -}).call(this); \ No newline at end of file +}).call(this);