Move _.max and _.min to the "Arrays" category and verify unit tests, rework _.isEqual [[DontEnum]] fix, and cleanup iteratorTemplate,

Former-commit-id: 9f4700b9db5767f314a44d6a83a3552d58ae6872
This commit is contained in:
John-David Dalton
2012-05-09 01:05:47 -04:00
parent 98435d2d92
commit 45f9d80a81
4 changed files with 267 additions and 235 deletions

257
lodash.js
View File

@@ -114,7 +114,7 @@
// the following branch is for iterating arrays and array-like objects
'<% if (arrayBranch) { %>' +
'var length = <%= firstArg %>.length; index = -1;' +
' <% if (objectBranch) { %>\nif (length === +length) {\n<% } %>' +
' <% if (objectBranch) { %>\nif (length === +length) {<% } %>\n' +
' <%= arrayBranch.beforeLoop %>;\n' +
' while (<%= arrayBranch.loopExp %>) {\n' +
' <%= arrayBranch.inLoop %>;\n' +
@@ -128,14 +128,14 @@
' if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == \'function\';\n<% } %>' +
' <%= objectBranch.beforeLoop %>;\n' +
' for (<%= objectBranch.loopExp %>) {' +
' <%' +
' \n<%' +
' if (hasDontEnumBug) {' +
' if (useHas) { %> if (<%= hasExp %>) {\n <% } %>' +
' <%= objectBranch.inLoop %>;\n<%' +
' if (useHas) { %> }\n<% }' +
' <%= objectBranch.inLoop %>;<%' +
' if (useHas) { %>\n }<% }' +
' }' +
' else {' +
' %>\n' +
' %>' +
// Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
// (if the prototype or a property on the prototype has been set)
@@ -153,11 +153,11 @@
// existing property and the `constructor` property of a prototype
// defaults to non-enumerable, Lo-Dash skips the `constructor`
// property when it infers it's iterating over a `prototype` object.
' <% if (hasDontEnumBug) { %>' +
' <% if (hasDontEnumBug) { %>\n' +
' var ctor = <%= iteratedObject %>.constructor,\n' +
' skipCtor = ctor && ctor.prototype === <%= iteratedObject %>;\n' +
' for (var j = 0; j < 7; j++) {\n' +
' index = shadowed[j];\n' +
' skipCtor = ctor && ctor.prototype === <%= iteratedObject %>;\n\n' +
' for (var k = 0; k < 7; k++) {\n' +
' index = shadowed[k];\n' +
' if (!(skipCtor && index == "constructor") && <%= hasExp %>) {\n' +
' <%= objectBranch.inLoop %>;\n' +
' }\n' +
@@ -230,29 +230,6 @@
}
};
/** Reusable iterator options for `_.max` */
var maxIteratorOptions = {
'top':
'var current, computed = -Infinity, result = computed;\n' +
'if (!callback) {\n' +
' if (isArray(collection) && collection[0] === +collection[0]) {\n' +
' try { return Math.max.apply(Math, collection); } catch(e) { }\n' +
' }\n' +
' if (isEmpty(collection)) {\n' +
' return result\n' +
' }\n' +
'} else if (thisArg) {\n' +
' callback = bind(callback, thisArg)\n' +
'}',
'inLoop':
'current = callback' +
' ? callback(collection[index], index, collection)' +
' : collection[index];\n' +
'if (current >= computed) {\n' +
' computed = current, result = collection[index]\n' +
'}'
};
/*--------------------------------------------------------------------------*/
/**
@@ -285,20 +262,6 @@
/*--------------------------------------------------------------------------*/
/**
* Iterates over an `object`, executing the `callback` for each value in the
* `collection`. The `callback` is invoked with 3 arguments, (value, key, object),
* and may terminate the loop by explicitly returning `false`.
*
* @private
* @param {Object} object The object to iterate over.
* @param {Function} callback The function called per iteration.
*/
var forOwn = createIterator({
'args': 'object, callback',
'inLoop': 'if (callback(object[index], index, object) === false) break'
});
/**
* Checks if a `value` is an array.
*
@@ -692,57 +655,6 @@
*/
var map = createIterator(forEachIteratorOptions, mapIteratorOptions);
/**
* Retrieves the maximum value of a `collection`. If `callback` is passed,
* it will be executed for each value in the `collection` to generate the
* criterion by which the value is ranked. The `callback` is invoked with 3
* arguments; for arrays they are (value, index, array) and for objects they
* are (value, key, object).
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object} collection The collection to iterate over.
* @param {Function} [callback] The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Mixed} Returns the maximum value.
* @example
*
* var stooges = [
* { 'name': 'moe', 'age': 40 },
* { 'name': 'larry', 'age': 50 },
* { 'name': 'curly', 'age': 60 }
* ];
*
* _.max(stooges, function(stooge) { return stooge.age; });
* // => { 'name': 'curly', 'age': 60 };
*/
var max = createIterator(forEachIteratorOptions, maxIteratorOptions);
/**
* Retrieves the minimum value of a `collection`. If `callback` is passed,
* it will be executed for each value in the `collection` to generate the
* criterion by which the value is ranked. The `callback` is invoked with 3
* arguments; for arrays they are (value, index, array) and for objects they
* are (value, key, object).
*
* @static
* @memberOf _
* @category Collections
* @param {Array|Object} collection The collection to iterate over.
* @param {Function} [callback] The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Mixed} Returns the minimum value.
* @example
*
* _.min([10, 5, 100, 2, 1000]);
* // => 2
*/
var min = createIterator(forEachIteratorOptions, maxIteratorOptions, {
'top': maxIteratorOptions.top.replace('-', '').replace('max', 'min'),
'inLoop': maxIteratorOptions.inLoop.replace('>=', '<')
});
/**
* Retrieves the value of a specified property from all values in a `collection`.
*
@@ -1317,6 +1229,114 @@
return -1;
}
/**
* Retrieves the maximum value of an `array`. If `callback` is passed,
* it will be executed for each value in the `array` to generate the
* criterion by which the value is ranked. The `callback` is invoked with 3
* arguments; (value, index, array).
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to iterate over.
* @param {Function} [callback] The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Mixed} Returns the maximum value.
* @example
*
* var stooges = [
* { 'name': 'moe', 'age': 40 },
* { 'name': 'larry', 'age': 50 },
* { 'name': 'curly', 'age': 60 }
* ];
*
* _.max(stooges, function(stooge) { return stooge.age; });
* // => { 'name': 'curly', 'age': 60 };
*/
function max(array, callback, thisArg) {
var current,
computed = -Infinity,
index = -1,
length = array.length,
result = computed;
if (!callback) {
// assume an array of numbers
if (array[0] === +array[0] && length < 25000) {
// some browsers, like Chrome and Firefox, have a limit on the number of
// arguments a function is allowed to accept before clipping the arguments
// or throwing an error
try {
return Math.max.apply(Math, array);
} catch(e) { }
}
if (!array.length) {
return result;
}
} else if (thisArg) {
callback = bind(callback, thisArg);
}
while (++index < length) {
current = callback ? callback(array[index], index, array) : array[index];
if (current >= computed) {
computed = current;
result = array[index];
}
}
return result;
}
/**
* Retrieves the minimum value of an `array`. If `callback` is passed,
* it will be executed for each value in the `array` to generate the
* criterion by which the value is ranked. The `callback` is invoked with 3
* arguments; (value, index, array).
*
* @static
* @memberOf _
* @category Arrays
* @param {Array} array The array to iterate over.
* @param {Function} [callback] The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Mixed} Returns the minimum value.
* @example
*
* _.min([10, 5, 100, 2, 1000]);
* // => 2
*/
function min(array, callback, thisArg) {
var current,
computed = Infinity,
index = -1,
length = array.length,
result = computed;
if (!callback) {
// assume an array of numbers
if (array[0] === +array[0] && length < 25000) {
// some browsers, like Chrome and Firefox, have a limit on the number of
// arguments a function is allowed to accept before clipping the arguments
// or throwing an error
try {
return Math.min.apply(Math, array);
} catch(e) { }
}
if (!array.length) {
return result;
}
} else if (thisArg) {
callback = bind(callback, thisArg);
}
while (++index < length) {
current = callback ? callback(array[index], index, array) : array[index];
if (current < computed) {
computed = current;
result = array[index];
}
}
return result;
}
/**
* Creates an array of numbers (positive and/or negative) progressing from
* `start` up to but not including `stop`. This method is a port of Python's
@@ -2299,7 +2319,8 @@
}
}
var result = true,
var index = -1,
result = true,
size = 0;
// add the first collection to the stack of traversed objects
@@ -2324,23 +2345,35 @@
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) {
return false;
}
// deep compare objects
forOwn(a, function(value, prop) {
// count the expected number of properties
size++;
// deep compare each member
result = hasOwnProperty.call(b, prop) && isEqual(value, b[prop], stack);
// exit loop if `result` is `false`
return result;
});
// ensure that both objects contain the same number of properties
// deep compare objects.
for (var prop in a) {
if (hasOwnProperty.call(a, prop)) {
// count the number of properties.
size++;
// deep compare each property value.
if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) {
break;
}
}
}
// ensure both objects have the same number of properties
if (result) {
forOwn(b, function() {
return !!(size--);
});
for (prop in b) {
if (hasOwnProperty.call(b, prop) && !(size--)) break;
}
result = !size;
}
// handle JScript [[DontEnum]] bug
if (result && hasDontEnumBug) {
while (++index < 7) {
prop = shadowed[index];
if (hasOwnProperty.call(a, prop)) {
if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) {
break;
}
}
}
}
}
// remove the first collection from the stack of traversed objects
stack.pop();