From 9e4192729375c8eab68913d36de9fbb8ffbbdfcc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 6 Dec 2011 17:33:43 -0500 Subject: [PATCH] rolling back to the previous implementation of 'each' ... cancels out #385 --- test/arrays.js | 12 +-- test/collections.js | 33 +++--- test/objects.js | 86 +++------------- underscore.js | 242 ++++++++++++++++---------------------------- 4 files changed, 117 insertions(+), 256 deletions(-) diff --git a/test/arrays.js b/test/arrays.js index 7e2c7328b..1c910355f 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -127,12 +127,9 @@ $(document).ready(function() { }); test("arrays: indexOf", function() { - var numbers = [1]; - numbers[2] = 3; + var numbers = [1, 2, 3]; numbers.indexOf = null; - equals(_.indexOf(numbers, 3), 2, 'can compute indexOf, even without the native function'); - equals(_.indexOf(numbers, void 0), -1, 'handles sparse arrays correctly'); - + equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3); equals(result, 1, 'works on an arguments object'); equals(_.indexOf(null, 2), -1, 'handles nulls properly'); @@ -151,13 +148,10 @@ $(document).ready(function() { }); test("arrays: lastIndexOf", function() { - var numbers = [1, 0, 1, 0, 0, 1, 0]; - numbers[8] = 0; + var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; numbers.lastIndexOf = null; equals(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); - equals(_.lastIndexOf(numbers, void 0), -1, 'handles sparse arrays correctly'); - var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0); equals(result, 5, 'works on an arguments object'); equals(_.indexOf(null, 2), -1, 'handles nulls properly'); diff --git a/test/collections.js b/test/collections.js index a3c2886e3..1726629eb 100644 --- a/test/collections.js +++ b/test/collections.js @@ -1,4 +1,4 @@ -$(document).ready(function($, undefined) { +$(document).ready(function() { module("Collections"); @@ -8,7 +8,7 @@ $(document).ready(function($, undefined) { }); var answers = []; - _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier : 5}); + _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); answers = []; @@ -16,11 +16,11 @@ $(document).ready(function($, undefined) { equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); answers = []; - var obj = {one: 1, two: 2, three: 3, toString: 1}; + var obj = {one : 1, two : 2, three : 3}; obj.constructor.prototype.four = 4; _.each(obj, function(value, key){ answers.push(key); }); + equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); delete obj.constructor.prototype.four; - equals(answers.sort().join(', '), 'one, three, toString, two', 'iterating over objects works, and ignores the object prototype.'); answer = null; _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); @@ -75,14 +75,15 @@ $(document).ready(function($, undefined) { ifnull = ex; } ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); - ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); - equals(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); - raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); - var sparseArray = []; - sparseArray[0] = 20; - sparseArray[2] = -5; - equals(_.reduce(sparseArray, function(a, b){ return a - b }), 25, 'initially-sparse arrays with no memo'); + ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); + + // Sparse arrays: + var sparseArray = []; + sparseArray[100] = 10; + sparseArray[200] = 20; + + equals(_.reduce(sparseArray, function(a, b){ return a + b }), 30, 'initially-sparse arrays with no memo'); }); test('collections: reduceRight', function() { @@ -102,14 +103,8 @@ $(document).ready(function($, undefined) { ifnull = ex; } ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); - ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); - equals(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); - raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); - var sparseArray = []; - sparseArray[0] = 20; - sparseArray[2] = -5; - equals(_.reduceRight(sparseArray, function(a, b){ return a - b }), -25, 'initially-sparse arrays with no memo'); + ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); }); test('collections: detect', function() { @@ -238,7 +233,7 @@ $(document).ready(function($, undefined) { equals(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements'); var numbers = _.toArray({one : 1, two : 2, three : 3}); - equals(numbers.sort().join(', '), '1, 2, 3', 'object flattened into array'); + equals(numbers.join(', '), '1, 2, 3', 'object flattened into array'); }); test('collections: size', function() { diff --git a/test/objects.js b/test/objects.js index 73c56376d..6d432033a 100644 --- a/test/objects.js +++ b/test/objects.js @@ -1,43 +1,22 @@ -$(document).ready(function($, undefined) { +$(document).ready(function() { module("Objects"); test("objects: keys", function() { - equals( - _.keys({one: 1, two: 2}).sort().join(', '), - 'one, two', - 'can extract the keys from an object' - ); - - equals( - _.keys({ - constructor: 'a', - hasOwnProperty: 'b', - isPrototypeOf: 'c', - propertyIsEnumerable: 'd', - toLocaleString: 'e', - toString: 'f', - valueOf: 'g' - }).sort().join(', '), - 'constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, toString, valueOf', - 'keys of shadowing properties' - ); - - var fn = function(){}; - fn.x = fn.y = fn.z = fn.prototype.a = 1; - equals(_.keys(fn).sort().join(', '), 'x, y, z', 'keys of functions works, and ignores the prototype property'); - + var exception = /object/; + equals(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); + // the test above is not safe because it relies on for-in enumeration order var a = []; a[1] = 0; equals(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95'); - raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values'); - raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values'); - raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives'); - raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives'); - raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives'); + raises(function() { _.keys(null); }, exception, 'throws an error for `null` values'); + raises(function() { _.keys(void 0); }, exception, 'throws an error for `undefined` values'); + raises(function() { _.keys(1); }, exception, 'throws an error for number primitives'); + raises(function() { _.keys('a'); }, exception, 'throws an error for string primitives'); + raises(function() { _.keys(true); }, exception, 'throws an error for boolean primitives'); }); test("objects: values", function() { - equals(_.values({one : 1, two : 2}).sort().join(', '), '1, 2', 'can extract the values from an object'); + equals(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); }); test("objects: functions", function() { @@ -51,38 +30,20 @@ $(document).ready(function($, undefined) { test("objects: extend", function() { var result; - var expected = { - constructor: 'a', - hasOwnProperty: 'b', - isPrototypeOf: 'c', - propertyIsEnumerable: 'd', - toLocaleString: 'e', - toString: 'f', - valueOf: 'g' - }; - equals(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another'); equals(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination'); equals(_.extend({x:'x'}, {a:'b'}).x, 'x', 'properties not in source dont get overriden'); - - result = _.extend({}, expected); - ok(_.isEqual(result, expected), 'extend with shadow properties'); - result = _.extend({x:'x'}, {a:'a'}, {b:'b'}); ok(_.isEqual(result, {x:'x', a:'a', b:'b'}), 'can extend from multiple source objects'); - result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'}); ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps'); - result = _.extend({}, {a: void 0, b: null}); equals(_.keys(result).join(''), 'b', 'extend does not copy undefined values'); }); test("objects: defaults", function() { var result; - var Options = function() { this.empty = ''; this.string = 'string'; this.zero = 0; }; - _.extend(Options.prototype, {nan: NaN, one:1}); - var options = new Options; + var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"}; _.defaults(options, {zero: 1, one: 10, twenty: 20}); equals(options.zero, 0, 'value exists'); @@ -96,14 +57,9 @@ $(document).ready(function($, undefined) { }); test("objects: clone", function() { - var toString = function() { return this.name; }; - var valueOf = function() { return this.name; }; - var moe = {name : 'moe', lucky : [13, 27, 34], toString: toString, valueOf: valueOf}; - + var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = _.clone(moe); equals(clone.name, 'moe', 'the clone as the attributes of the original'); - equals(clone.toString, toString, 'cloned own toString method'); - equals(clone.valueOf, valueOf, 'cloned own valueOf method'); clone.name = 'curly'; ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); @@ -126,16 +82,6 @@ $(document).ready(function($, undefined) { } Second.prototype.value = 2; - var obj = { - constructor: 'a', - hasOwnProperty: 'b', - isPrototypeOf: 'c', - propertyIsEnumerable: 'd', - toLocaleString: 'e', - toString: 'f', - valueOf: 'g' - }; - // Basic equality and identity comparisons. ok(_.isEqual(null, null), "`null` is equal to `null`"); ok(_.isEqual(), "`undefined` is equal to `undefined`"); @@ -262,10 +208,6 @@ $(document).ready(function($, undefined) { ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects"); ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent"); - // Objects with properties that shadow non-enumerable ones. - ok(!_.isEqual({}, {toString: 1}), "Object with custom toString is not equal to {}"); - ok(_.isEqual({toString: 1, valueOf: 2}, {toString: 1, valueOf: 2}), "Objects with equivalent shadow properties"); - // `A` contains nested objects and arrays. a = { name: new String("Moe Howard"), @@ -416,7 +358,7 @@ $(document).ready(function($, undefined) { test("objects: isEmpty", function() { ok(!_([1]).isEmpty(), '[1] is not empty'); ok(_.isEmpty([]), '[] is empty'); - ok(!_.isEmpty({valueOf: 1}), '{valueOf: 1} is not empty'); + ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); ok(_.isEmpty({}), '{} is empty'); ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty'); ok(_.isEmpty(null), 'null is empty'); @@ -446,7 +388,7 @@ $(document).ready(function($, undefined) { parent.iNaN = NaN;\ parent.iNull = null;\ parent.iBoolean = new Boolean(false);\ - parent.iUndefined = void 0;\ + parent.iUndefined = undefined;\ " ); iDoc.close(); diff --git a/underscore.js b/underscore.js index 1640fecf8..86e262343 100644 --- a/underscore.js +++ b/underscore.js @@ -21,14 +21,11 @@ var breaker = {}; // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, - ObjProto = Object.prototype, - FuncProto = Function.prototype; + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. - var concat = ArrayProto.concat, - push = ArrayProto.push, - slice = ArrayProto.slice, + var slice = ArrayProto.slice, + unshift = ArrayProto.unshift, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; @@ -48,12 +45,6 @@ nativeKeys = Object.keys, nativeBind = FuncProto.bind; - // List of possible shadowed properties on Object.prototype. - var shadowed = [ - 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', - 'toLocaleString', 'toString', 'valueOf' - ]; - // Create a safe reference to the Underscore object for use below. var _ = function(obj) { return new wrapper(obj); }; @@ -65,11 +56,13 @@ exports = module.exports = _; } exports._ = _; - // Register as a named module with AMD. } else if (typeof define === 'function' && define.amd) { - define('underscore', function() { return _; }); - // Exported as a string, for Closure Compiler "advanced" mode. + // Register as a named module with AMD. + define('underscore', function() { + return _; + }); } else { + // Exported as a string, for Closure Compiler "advanced" mode. root['_'] = _; } @@ -85,71 +78,17 @@ var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { - return obj.forEach(iterator, context); - } - var fn = iterator; - var i = -1; - var l = obj.length; - // We optimize for common use by only binding a context when it's passed. - if (context) { - iterator = function() { return fn.call(context, obj[i], i, obj); }; - } - // If we're dealing with an array or array-like object... - if (l === l >>> 0) { - while (++i < l) { - if (i in obj && iterator(obj[i], i, obj) == breaker) return; + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; } - // Otherwise, delegate to `forProps` over an object's keys and values. } else { - forProps(obj, iterator, true); - } - }; - - // A simple each, for dealing with non-sparse arrays and arguments objects. - var simpleEach = function(obj, iterator, index) { - index || (index = 0); - for (var l = obj.length; index < l; index++) { - iterator(obj[index]); - } - }; - - // IE < 9 makes properties, shadowing non-enumerable ones, non-enumerable too. - var forShadowed = !{valueOf:0}.propertyIsEnumerable('valueOf') && - function(obj, iterator) { - // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing - // property and the `constructor` property of a prototype defaults to - // non-enumerable, we manually skip the `constructor` property when we - // think we are iterating over a `prototype` object. - var ctor = obj.constructor; - var skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor; - for (var key, i = 0; key = shadowed[i]; i++) { - if (!(skipCtor && key == 'constructor') && - hasOwnProperty.call(obj, key) && - iterator(obj[key], key, obj) === breaker) { - break; + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; } } - }; - - // Iterates over an object's properties, executing the `callback` for each. - var forProps = function(obj, iterator, ownOnly) { - var done = !obj; - var skipProto = typeof obj == 'function'; - for (var key in obj) { - // Firefox < 3.6, Opera > 9.50 - Opera < 12, and Safari < 5.1 - // (if the prototype or a property on the prototype has been set) - // incorrectly set a function's `prototype` property `[[Enumerable]]` value - // to true. Because of this we standardize on skipping the the `prototype` - // property of functions regardless of their `[[Enumerable]]` value. - if (done = - !(skipProto && key == 'prototype') && - (!ownOnly || ownOnly && hasOwnProperty.call(obj, key)) && - iterator(obj[key], key, obj) === breaker) { - break; - } - } - if (!done && forShadowed) { - forShadowed(obj, iterator); } }; @@ -168,7 +107,7 @@ // **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; + var initial = memo !== void 0; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); @@ -182,22 +121,20 @@ memo = iterator.call(context, memo, value, index, list); } }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); + if (!initial) throw new TypeError("Reduce of empty array with no initial value"); 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; if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } - var reversed = _.toArray(obj).reverse(); - if (context && !initial) iterator = _.bind(iterator, context); - return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); + var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); + return _.reduce(reversed, iterator, memo, context); }; // Return the first value which passes a truth test. Aliased as `detect`. @@ -252,7 +189,7 @@ // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); + iterator = iterator || _.identity; var result = false; if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); @@ -334,8 +271,7 @@ criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; + var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); }; @@ -356,8 +292,7 @@ // be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator) { iterator || (iterator = _.identity); - var low = 0; - var high = array.length; + var low = 0, high = array.length; while (low < high) { var mid = (low + high) >> 1; iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; @@ -367,9 +302,10 @@ // Safely convert anything iterable into a real, live array. _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable) || _.isArguments(iterable)) return slice.call(iterable); + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + if (_.isArray(iterable)) return slice.call(iterable); + if (_.isArguments(iterable)) return slice.call(iterable); return _.values(iterable); }; @@ -388,7 +324,7 @@ return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; - // Returns everything but the last entry of the array. Especially useful on + // Returns everything but the last entry of the array. Especcialy useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. @@ -484,9 +420,10 @@ // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { - var length = _.max(_.pluck(arguments, 'length')); + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(arguments, i); + for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); return results; }; @@ -535,6 +472,7 @@ range[idx++] = start; start += step; } + return range; }; @@ -554,10 +492,10 @@ if (!_.isFunction(func)) throw new TypeError; args = slice.call(arguments, 2); return bound = function() { - if (!(this instanceof bound)) return func.apply(context, concat.apply(args, arguments)); + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; - var result = func.apply(self, concat.apply(args, arguments)); + var result = func.apply(self, args.concat(slice.call(arguments))); if (Object(result) === result) return result; return self; }; @@ -566,20 +504,16 @@ // Bind all of an object's methods to that object. Useful for ensuring that // all callbacks defined on an object belong to it. _.bindAll = function(obj) { - var i = 1; - var funcs = arguments; - if (funcs.length < 2) { - i = 0; - funcs = _.functions(obj); - } - simpleEach(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }, i); + var funcs = slice.call(arguments, 1); + if (funcs.length == 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { - hasher || (hasher = _.identity); var memo = {}; + hasher || (hasher = _.identity); return function() { var key = hasher.apply(this, arguments); return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); @@ -654,7 +588,7 @@ // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { - var args = concat.apply([func], arguments); + var args = [func].concat(slice.call(arguments)); return wrapper.apply(this, args); }; }; @@ -662,9 +596,9 @@ // 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 funcs = slice.call(arguments); return function() { - var args = arguments; + var args = slice.call(arguments); for (var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } @@ -688,9 +622,7 @@ _.keys = nativeKeys || function(obj) { if (obj !== Object(obj)) throw new TypeError('Invalid object'); var keys = []; - forProps(obj, function(value, key) { - keys[keys.length] = key; - }, true); + for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; return keys; }; @@ -703,29 +635,29 @@ // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; - forProps(obj, function(value, key) { - if (_.isFunction(value)) names[names.length] = key; - }); + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { - simpleEach(arguments, function(source) { - forProps(source, function(value, key) { - if (value !== void 0) obj[key] = value; - }); - }, 1); + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (source[prop] !== void 0) obj[prop] = source[prop]; + } + }); return obj; }; // Fill in a given object with default properties. _.defaults = function(obj) { - simpleEach(arguments, function(source) { - forProps(source, function(value, key) { - if (obj[key] == null) obj[key] = value; - }); - }, 1); + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + }); return obj; }; @@ -754,8 +686,8 @@ if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. - if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); - if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); + if (_.isFunction(a.isEqual)) return a.isEqual(b); + if (_.isFunction(b.isEqual)) return b.isEqual(a); // Compare `[[Class]]` names. var className = toString.call(a); if (className != toString.call(b)) return false; @@ -764,11 +696,13 @@ case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return a == String(b); + return String(a) == String(b); case '[object Number]': + a = +a; + b = +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); + 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 @@ -808,19 +742,21 @@ } } else { // Objects with different constructors are not equivalent. - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; + if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; // Deep compare objects. - forProps(a, function(value, key) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = hasOwnProperty.call(b, key) && eq(value, b[key], stack))) return breaker; - }, true); + for (var key in a) { + if (hasOwnProperty.call(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; + } + } // Ensure that both objects contain the same number of properties. if (result) { - forProps(b, function() { - return !(size--) && breaker; - }, true); + for (key in b) { + if (hasOwnProperty.call(b, key) && !(size--)) break; + } result = !size; } } @@ -837,15 +773,9 @@ // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { - var result = toString.call(obj); - if (result == '[object Array]' || result == '[object String]') { - return !obj.length; - } - forProps(obj, function() { - result = false; - return breaker; - }, true); - return !!result; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; + return true; }; // Is a given value a DOM element? @@ -865,11 +795,11 @@ }; // Is a given variable an arguments object? - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; - - if (!_.isArguments(arguments)) { + if (toString.call(arguments) == '[object Arguments]') { + _.isArguments = function(obj) { + return toString.call(obj) == '[object Arguments]'; + }; + } else { _.isArguments = function(obj) { return !!(obj && hasOwnProperty.call(obj, 'callee')); }; @@ -949,7 +879,7 @@ // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) { - simpleEach(_.functions(obj), function(name){ + each(_.functions(obj), function(name){ addToWrapper(name, _[name] = obj[name]); }); }; @@ -1019,8 +949,8 @@ // A method to easily add functions to the OOP wrapper. var addToWrapper = function(name, func) { wrapper.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); + var args = slice.call(arguments); + unshift.call(args, this._wrapped); return result(func.apply(_, args), this._chain); }; }; @@ -1029,7 +959,7 @@ _.mixin(_); // Add all mutator Array functions to the wrapper. - simpleEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { method.apply(this._wrapped, arguments); @@ -1038,7 +968,7 @@ }); // Add all accessor Array functions to the wrapper. - simpleEach(['concat', 'join', 'slice'], function(name) { + each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; wrapper.prototype[name] = function() { return result(method.apply(this._wrapped, arguments), this._chain);