mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-10 10:57:49 +00:00
Remove custom isEqual checks from _.isEqual and custom clone checks from _.clone and simply _.clone, _.isEqual, and _.merge.
Former-commit-id: 45e90ab1494e46e281265f660c87e27f59581308
This commit is contained in:
@@ -44,7 +44,6 @@
|
|||||||
'callee',
|
'callee',
|
||||||
'className',
|
'className',
|
||||||
'compareAscending',
|
'compareAscending',
|
||||||
'data',
|
|
||||||
'forIn',
|
'forIn',
|
||||||
'found',
|
'found',
|
||||||
'funcs',
|
'funcs',
|
||||||
@@ -64,13 +63,12 @@
|
|||||||
'properties',
|
'properties',
|
||||||
'property',
|
'property',
|
||||||
'propsLength',
|
'propsLength',
|
||||||
'recursive',
|
|
||||||
'source',
|
'source',
|
||||||
'sources',
|
'stackA',
|
||||||
|
'stackB',
|
||||||
'stackLength',
|
'stackLength',
|
||||||
'target',
|
'target',
|
||||||
'valueProp',
|
'valueProp'
|
||||||
'values'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Used to minify `compileIterator` option properties */
|
/** Used to minify `compileIterator` option properties */
|
||||||
@@ -118,7 +116,6 @@
|
|||||||
'chain',
|
'chain',
|
||||||
'clearTimeout',
|
'clearTimeout',
|
||||||
'clone',
|
'clone',
|
||||||
'clones',
|
|
||||||
'collect',
|
'collect',
|
||||||
'compact',
|
'compact',
|
||||||
'compose',
|
'compose',
|
||||||
@@ -214,9 +211,6 @@
|
|||||||
'sortBy',
|
'sortBy',
|
||||||
'sortedIndex',
|
'sortedIndex',
|
||||||
'source',
|
'source',
|
||||||
'sources',
|
|
||||||
'stackA',
|
|
||||||
'stackB',
|
|
||||||
'tail',
|
'tail',
|
||||||
'take',
|
'take',
|
||||||
'tap',
|
'tap',
|
||||||
@@ -305,10 +299,10 @@
|
|||||||
// remove debug sourceURL use in `_.template`
|
// remove debug sourceURL use in `_.template`
|
||||||
source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, '');
|
source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, '');
|
||||||
|
|
||||||
// minify internal properties used by 'compareAscending', `_.clone`, `_.isEqual`, `_.merge`, and `_.sortBy`
|
// minify internal properties used by 'compareAscending', `_.merge`, and `_.sortBy`
|
||||||
(function() {
|
(function() {
|
||||||
var properties = ['clones', 'criteria', 'index', 'sources', 'thorough', 'value', 'values'],
|
var properties = ['criteria', 'index', 'value'],
|
||||||
snippets = source.match(/( +)(?:function (?:clone|compareAscending|isEqual)|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
|
snippets = source.match(/( +)(?:function compareAscending|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
|
||||||
|
|
||||||
if (!snippets) {
|
if (!snippets) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
140
lodash.js
140
lodash.js
@@ -1051,10 +1051,9 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a clone of `value`. If `deep` is `true`, all nested objects will
|
* Creates a clone of `value`. If `deep` is `true`, all nested objects will
|
||||||
* also be cloned otherwise they will be assigned by reference. If a value has
|
* also be cloned otherwise they will be assigned by reference. Functions, DOM
|
||||||
* a `clone` method it will be used to perform the clone. Functions, DOM nodes,
|
* nodes, `arguments` objects, and objects created by constructors other than
|
||||||
* `arguments` objects, and objects created by constructors other than `Object`
|
* `Object` are **not** cloned.
|
||||||
* are **not** cloned unless they have a custom `clone` method.
|
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @memberOf _
|
* @memberOf _
|
||||||
@@ -1063,9 +1062,9 @@
|
|||||||
* @param {Boolean} deep A flag to indicate a deep clone.
|
* @param {Boolean} deep A flag to indicate a deep clone.
|
||||||
* @param {Object} [guard] Internally used to allow this method to work with
|
* @param {Object} [guard] Internally used to allow this method to work with
|
||||||
* others like `_.map` without using their callback `index` argument for `deep`.
|
* others like `_.map` without using their callback `index` argument for `deep`.
|
||||||
* @param {Object} [data={}] Internally used to track traversed objects to avoid
|
* @param {Array} [stackA=[]] Internally used to track traversed source objects.
|
||||||
* circular references and indicate whether to perform a more thorough clone
|
* @param {Array} [stackB=[]] Internally used to associate clones with their
|
||||||
* of non-object values.
|
* source counterparts.
|
||||||
* @returns {Mixed} Returns the cloned `value`.
|
* @returns {Mixed} Returns the cloned `value`.
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
@@ -1086,28 +1085,15 @@
|
|||||||
* shallow[0] === stooges[0];
|
* shallow[0] === stooges[0];
|
||||||
* // => false
|
* // => false
|
||||||
*/
|
*/
|
||||||
function clone(value, deep, guard, data) {
|
function clone(value, deep, guard, stackA, stackB) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (guard) {
|
if (guard) {
|
||||||
deep = false;
|
deep = false;
|
||||||
}
|
}
|
||||||
// init internal data
|
|
||||||
data || (data = { 'thorough': null });
|
|
||||||
|
|
||||||
// avoid slower checks on primitives
|
|
||||||
if (data.thorough == null) {
|
|
||||||
// primitives passed from iframes use the primary document's native prototypes
|
|
||||||
data.thorough = !!(BoolProto.clone || NumberProto.clone || StringProto.clone);
|
|
||||||
}
|
|
||||||
// use custom `clone` method if available
|
|
||||||
var isObj = objectTypes[typeof value];
|
|
||||||
if ((isObj || data.thorough) && value.clone && isFunction(value.clone)) {
|
|
||||||
data.thorough = null;
|
|
||||||
return value.clone(deep);
|
|
||||||
}
|
|
||||||
// inspect [[Class]]
|
// inspect [[Class]]
|
||||||
|
var isObj = objectTypes[typeof value];
|
||||||
if (isObj) {
|
if (isObj) {
|
||||||
// don't clone `arguments` objects, functions, or non-object Objects
|
// don't clone `arguments` objects, functions, or non-object Objects
|
||||||
var className = toString.call(value);
|
var className = toString.call(value);
|
||||||
@@ -1141,33 +1127,34 @@
|
|||||||
return ctor(value.source, reFlags.exec(value));
|
return ctor(value.source, reFlags.exec(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var clones = data.clones || (data.clones = []),
|
|
||||||
sources = data.sources || (data.sources = []),
|
|
||||||
length = clones.length;
|
|
||||||
|
|
||||||
// check for circular references and return corresponding clone
|
// check for circular references and return corresponding clone
|
||||||
|
stackA || (stackA = []);
|
||||||
|
stackB || (stackB = []);
|
||||||
|
|
||||||
|
var length = stackA.length;
|
||||||
while (length--) {
|
while (length--) {
|
||||||
if (sources[length] == value) {
|
if (stackA[length] == value) {
|
||||||
return clones[length];
|
return stackB[length];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// init cloned object
|
// init cloned object
|
||||||
var result = isArr ? ctor(length = value.length) : {};
|
var result = isArr ? ctor(length = value.length) : {};
|
||||||
|
|
||||||
// add current clone and original source value to the stack of traversed objects
|
// add the source value to the stack of traversed objects
|
||||||
clones.push(result);
|
// and associate it with its clone
|
||||||
sources.push(value);
|
stackA.push(value);
|
||||||
|
stackB.push(result);
|
||||||
|
|
||||||
// recursively populate clone (susceptible to call stack limits)
|
// recursively populate clone (susceptible to call stack limits)
|
||||||
if (isArr) {
|
if (isArr) {
|
||||||
var index = -1;
|
var index = -1;
|
||||||
while (++index < length) {
|
while (++index < length) {
|
||||||
result[index] = clone(value[index], deep, null, data);
|
result[index] = clone(value[index], deep, null, stackA, stackB);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
forOwn(value, function(objValue, key) {
|
forOwn(value, function(objValue, key) {
|
||||||
result[key] = clone(objValue, deep, null, data);
|
result[key] = clone(objValue, deep, null, stackA, stackB);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -1418,17 +1405,15 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a deep comparison between two values to determine if they are
|
* Performs a deep comparison between two values to determine if they are
|
||||||
* equivalent to each other. If a value has an `isEqual` method it will be
|
* equivalent to each other.
|
||||||
* used to perform the comparison.
|
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @memberOf _
|
* @memberOf _
|
||||||
* @category Objects
|
* @category Objects
|
||||||
* @param {Mixed} a The value to compare.
|
* @param {Mixed} a The value to compare.
|
||||||
* @param {Mixed} b The other value to compare.
|
* @param {Mixed} b The other value to compare.
|
||||||
* @param {Object} [data={}] Internally used track traversed objects to avoid
|
* @param {Object} [stackA=[]] Internally used track traversed `a` objects.
|
||||||
* circular references and indicate whether to perform a more thorough comparison
|
* @param {Object} [stackB=[]] Internally used track traversed `b` objects.
|
||||||
* of non-object values.
|
|
||||||
* @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
|
* @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
@@ -1441,39 +1426,21 @@
|
|||||||
* _.isEqual(moe, clone);
|
* _.isEqual(moe, clone);
|
||||||
* // => true
|
* // => true
|
||||||
*/
|
*/
|
||||||
function isEqual(a, b, data) {
|
function isEqual(a, b, stackA, stackB) {
|
||||||
// a strict comparison is necessary because `null == undefined`
|
// a strict comparison is necessary because `null == undefined`
|
||||||
if (a == null || b == null) {
|
if (a == null || b == null) {
|
||||||
return a === b;
|
return a === b;
|
||||||
}
|
}
|
||||||
// init internal data
|
|
||||||
data || (data = { 'thorough': null });
|
|
||||||
|
|
||||||
// avoid slower checks on non-objects
|
|
||||||
if (data.thorough == null) {
|
|
||||||
// primitives passed from iframes use the primary document's native prototypes
|
|
||||||
data.thorough = !!(BoolProto.isEqual || NumberProto.isEqual || StringProto.isEqual);
|
|
||||||
}
|
|
||||||
if (objectTypes[typeof a] || objectTypes[typeof b] || data.thorough) {
|
|
||||||
// unwrap any LoDash wrapped values
|
|
||||||
a = a.__wrapped__ || a;
|
|
||||||
b = b.__wrapped__ || b;
|
|
||||||
|
|
||||||
// use custom `isEqual` method if available
|
|
||||||
if (a.isEqual && isFunction(a.isEqual)) {
|
|
||||||
data.thorough = null;
|
|
||||||
return a.isEqual(b);
|
|
||||||
}
|
|
||||||
if (b.isEqual && isFunction(b.isEqual)) {
|
|
||||||
data.thorough = null;
|
|
||||||
return b.isEqual(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// exit early for identical values
|
// exit early for identical values
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
// treat `+0` vs. `-0` as not equal
|
// treat `+0` vs. `-0` as not equal
|
||||||
return a !== 0 || (1 / a == 1 / b);
|
return a !== 0 || (1 / a == 1 / b);
|
||||||
}
|
}
|
||||||
|
// unwrap any LoDash wrapped values
|
||||||
|
if (objectTypes[typeof a] || objectTypes[typeof b]) {
|
||||||
|
a = a.__wrapped__ || a;
|
||||||
|
b = b.__wrapped__ || b;
|
||||||
|
}
|
||||||
// compare [[Class]] names
|
// compare [[Class]] names
|
||||||
var className = toString.call(a);
|
var className = toString.call(a);
|
||||||
if (className != toString.call(b)) {
|
if (className != toString.call(b)) {
|
||||||
@@ -1514,10 +1481,10 @@
|
|||||||
// assume cyclic structures are equal
|
// assume cyclic structures are equal
|
||||||
// the algorithm for detecting cyclic structures is adapted from ES 5.1
|
// the algorithm for detecting cyclic structures is adapted from ES 5.1
|
||||||
// section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3)
|
// section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3)
|
||||||
var stackA = data.stackA || (data.stackA = []),
|
stackA || (stackA = []);
|
||||||
stackB = data.stackB || (data.stackB = []),
|
stackB || (stackB = []);
|
||||||
length = stackA.length;
|
|
||||||
|
|
||||||
|
var length = stackA.length;
|
||||||
while (length--) {
|
while (length--) {
|
||||||
if (stackA[length] == a) {
|
if (stackA[length] == a) {
|
||||||
return stackB[length] == b;
|
return stackB[length] == b;
|
||||||
@@ -1541,7 +1508,7 @@
|
|||||||
if (result) {
|
if (result) {
|
||||||
// deep compare the contents, ignoring non-numeric properties
|
// deep compare the contents, ignoring non-numeric properties
|
||||||
while (size--) {
|
while (size--) {
|
||||||
if (!(result = isEqual(a[size], b[size], data))) {
|
if (!(result = isEqual(a[size], b[size], stackA, stackB))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1565,7 +1532,7 @@
|
|||||||
// count the number of properties.
|
// count the number of properties.
|
||||||
size++;
|
size++;
|
||||||
// deep compare each property value.
|
// deep compare each property value.
|
||||||
if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], data))) {
|
if (!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stackA, stackB))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1585,7 +1552,7 @@
|
|||||||
while (++index < 7) {
|
while (++index < 7) {
|
||||||
prop = shadowed[index];
|
prop = shadowed[index];
|
||||||
if (hasOwnProperty.call(a, prop) &&
|
if (hasOwnProperty.call(a, prop) &&
|
||||||
!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], data))) {
|
!(hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stackA, stackB))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1804,8 +1771,9 @@
|
|||||||
* @param {Object} [source1, source2, ...] The source objects.
|
* @param {Object} [source1, source2, ...] The source objects.
|
||||||
* @param {Object} [indicator] Internally used to indicate that the `stack`
|
* @param {Object} [indicator] Internally used to indicate that the `stack`
|
||||||
* argument is an array of traversed objects instead of another source object.
|
* argument is an array of traversed objects instead of another source object.
|
||||||
* @param {Object} [data={}] Internally used to track traversed objects to avoid
|
* @param {Array} [stackA=[]] Internally used to track traversed source objects.
|
||||||
* circular references.
|
* @param {Array} [stackB=[]] Internally used to associate clones with their
|
||||||
|
* source counterparts.
|
||||||
* @returns {Object} Returns the destination object.
|
* @returns {Object} Returns the destination object.
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
@@ -1825,25 +1793,34 @@
|
|||||||
var merge = createIterator(extendIteratorOptions, {
|
var merge = createIterator(extendIteratorOptions, {
|
||||||
'args': 'object, source, indicator',
|
'args': 'object, source, indicator',
|
||||||
'top':
|
'top':
|
||||||
'var isArr, recursive = indicator == isPlainObject,\n' +
|
'var argsLength, isArr, stackA, stackB,\n' +
|
||||||
' data = recursive ? arguments[3] : { values: [], sources: [] };\n' +
|
' args = arguments, argsIndex = 0;\n' +
|
||||||
'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' +
|
'if (indicator == isPlainObject) {\n' +
|
||||||
' if (iteratee = arguments[argsIndex]) {',
|
' argsLength = 2;\n' +
|
||||||
|
' stackA = args[3];\n' +
|
||||||
|
' stackB = args[4]\n' +
|
||||||
|
'} else {\n' +
|
||||||
|
' argsLength = args.length;\n' +
|
||||||
|
' stackA = [];\n' +
|
||||||
|
' stackB = []\n' +
|
||||||
|
'}\n' +
|
||||||
|
'while (++argsIndex < argsLength) {\n' +
|
||||||
|
' if (iteratee = args[argsIndex]) {',
|
||||||
'inLoop':
|
'inLoop':
|
||||||
'if ((source = value) && ((isArr = isArray(source)) || isPlainObject(source))) {\n' +
|
'if ((source = value) && ((isArr = isArray(source)) || isPlainObject(source))) {\n' +
|
||||||
' var found = false, values = data.values, sources = data.sources, stackLength = sources.length;\n' +
|
' var found = false, stackLength = stackA.length;\n' +
|
||||||
' while (stackLength--) {\n' +
|
' while (stackLength--) {\n' +
|
||||||
' if (found = sources[stackLength] == source) break\n' +
|
' if (found = stackA[stackLength] == source) break\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
' if (found) {\n' +
|
' if (found) {\n' +
|
||||||
' result[index] = values[stackLength]\n' +
|
' result[index] = stackB[stackLength]\n' +
|
||||||
' } else {\n' +
|
' } else {\n' +
|
||||||
' values.push(value = (value = result[index]) && isArr\n' +
|
' stackA.push(source);\n' +
|
||||||
|
' stackB.push(value = (value = result[index]) && isArr\n' +
|
||||||
' ? (isArray(value) ? value : [])\n' +
|
' ? (isArray(value) ? value : [])\n' +
|
||||||
' : (isPlainObject(value) ? value : {})\n' +
|
' : (isPlainObject(value) ? value : {})\n' +
|
||||||
' );\n' +
|
' );\n' +
|
||||||
' sources.push(source);\n' +
|
' result[index] = callee(value, source, isPlainObject, stackA, stackB)\n' +
|
||||||
' result[index] = callee(value, source, isPlainObject, data)\n' +
|
|
||||||
' }\n' +
|
' }\n' +
|
||||||
'} else if (source != null) {\n' +
|
'} else if (source != null) {\n' +
|
||||||
' result[index] = source\n' +
|
' result[index] = source\n' +
|
||||||
@@ -2446,8 +2423,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the `collection`, to an array. Useful for converting the
|
* Converts the `collection`, to an array.
|
||||||
* `arguments` object.
|
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @memberOf _
|
* @memberOf _
|
||||||
|
|||||||
24
test/test.js
24
test/test.js
@@ -260,19 +260,6 @@
|
|||||||
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c && clone !== object);
|
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c && clone !== object);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should clone using Klass#clone', function() {
|
|
||||||
var object = new Klass;
|
|
||||||
Klass.prototype.clone = function() { return new Klass; };
|
|
||||||
|
|
||||||
var clone = _.clone(object);
|
|
||||||
ok(clone !== object && clone instanceof Klass);
|
|
||||||
|
|
||||||
clone = _.clone(object, true);
|
|
||||||
ok(clone !== object && clone instanceof Klass);
|
|
||||||
|
|
||||||
delete Klass.prototype.clone;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should clone problem JScript properties (test in IE < 9)', function() {
|
test('should clone problem JScript properties (test in IE < 9)', function() {
|
||||||
deepEqual(_.clone(shadowed), shadowed);
|
deepEqual(_.clone(shadowed), shadowed);
|
||||||
ok(_.clone(shadowed) != shadowed);
|
ok(_.clone(shadowed) != shadowed);
|
||||||
@@ -755,17 +742,6 @@
|
|||||||
equal(_.isEqual(args1, args3), false);
|
equal(_.isEqual(args1, args3), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should respect custom `isEqual` result despite objects strict equaling each other', function() {
|
|
||||||
var object = { 'isEqual': function() { return false; } };
|
|
||||||
equal(_.isEqual(object, object), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should use custom `isEqual` methods on primitives', function() {
|
|
||||||
Boolean.prototype.isEqual = function() { return true; };
|
|
||||||
equal(_.isEqual(true, false), true);
|
|
||||||
delete Boolean.prototype.isEqual;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
|
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
|
||||||
equal(_.isEqual(shadowed, {}), false);
|
equal(_.isEqual(shadowed, {}), false);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user