mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-11 11:27:50 +00:00
Add deep clone support via the deep argument to _.clone.
Former-commit-id: d4fad45364489efb957d29e201846e8c1875b9ed
This commit is contained in:
15
build.js
15
build.js
@@ -150,7 +150,7 @@
|
|||||||
'bind': [],
|
'bind': [],
|
||||||
'bindAll': ['bind', 'functions'],
|
'bindAll': ['bind', 'functions'],
|
||||||
'chain': ['mixin'],
|
'chain': ['mixin'],
|
||||||
'clone': ['extend', 'isArray'],
|
'clone': ['extend', 'forOwn', 'isArguments'],
|
||||||
'compact': [],
|
'compact': [],
|
||||||
'compose': [],
|
'compose': [],
|
||||||
'contains': [],
|
'contains': [],
|
||||||
@@ -774,17 +774,12 @@
|
|||||||
|
|
||||||
// build replacement code
|
// build replacement code
|
||||||
lodash.forOwn({
|
lodash.forOwn({
|
||||||
'Arguments': 'argsClass',
|
|
||||||
'Date': 'dateClass',
|
'Date': 'dateClass',
|
||||||
'Function': 'funcClass',
|
'Function': 'funcClass',
|
||||||
'Number': 'numberClass',
|
'Number': 'numberClass',
|
||||||
'RegExp': 'regexpClass',
|
'RegExp': 'regexpClass',
|
||||||
'String': 'stringClass'
|
'String': 'stringClass'
|
||||||
}, function(value, key) {
|
}, function(value, key) {
|
||||||
// skip `isArguments` if not a mobile build
|
|
||||||
if (!isMobile && key == 'Arguments') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var funcName = 'is' + key,
|
var funcName = 'is' + key,
|
||||||
funcCode = matchFunction(source, funcName);
|
funcCode = matchFunction(source, funcName);
|
||||||
|
|
||||||
@@ -887,15 +882,19 @@
|
|||||||
// remove IE `shift` and `splice` fix from mutator Array functions mixin
|
// remove IE `shift` and `splice` fix from mutator Array functions mixin
|
||||||
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, '');
|
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, '');
|
||||||
|
|
||||||
// remove `noCharByIndex` from `_.reduceRight`
|
// remove `noArgsClass` from `_.clone` and `_.size`
|
||||||
source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
|
source = source.replace(/ *\|\| *\(noArgsClass *&[^)]+?\)\)/g, '');
|
||||||
|
|
||||||
// remove `noArraySliceOnStrings` from `_.toArray`
|
// remove `noArraySliceOnStrings` from `_.toArray`
|
||||||
source = source.replace(/noArraySliceOnStrings *\?[^:]+: *([^)]+)/g, '$1');
|
source = source.replace(/noArraySliceOnStrings *\?[^:]+: *([^)]+)/g, '$1');
|
||||||
|
|
||||||
|
// remove `noCharByIndex` from `_.reduceRight`
|
||||||
|
source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
|
||||||
|
|
||||||
source = removeVar(source, 'extendIteratorOptions');
|
source = removeVar(source, 'extendIteratorOptions');
|
||||||
source = removeVar(source, 'hasDontEnumBug');
|
source = removeVar(source, 'hasDontEnumBug');
|
||||||
source = removeVar(source, 'iteratorTemplate');
|
source = removeVar(source, 'iteratorTemplate');
|
||||||
|
source = removeVar(source, 'noArgsClass');
|
||||||
source = removeVar(source, 'noArraySliceOnStrings');
|
source = removeVar(source, 'noArraySliceOnStrings');
|
||||||
source = removeVar(source, 'noCharByIndex');
|
source = removeVar(source, 'noCharByIndex');
|
||||||
source = removeIsArgumentsFallback(source);
|
source = removeIsArgumentsFallback(source);
|
||||||
|
|||||||
@@ -220,6 +220,13 @@
|
|||||||
// Closure Compiler errors trying to minify them
|
// Closure Compiler errors trying to minify them
|
||||||
source = source.replace(/(arrayLikeClasses =)[\s\S]+?= *true/g, "$1{'[object Arguments]': true, '[object Array]': true, '[object String]': true }");
|
source = source.replace(/(arrayLikeClasses =)[\s\S]+?= *true/g, "$1{'[object Arguments]': true, '[object Array]': true, '[object String]': true }");
|
||||||
|
|
||||||
|
// manually convert `cloneableClasses` property assignments because
|
||||||
|
// Closure Compiler errors trying to minify them
|
||||||
|
source = source.replace(/(cloneableClasses =)[\s\S]+?= *true/g,
|
||||||
|
"$1{'[object Array]': true, '[object Boolean]': true, '[object Date]': true, " +
|
||||||
|
"'[object Number]': true, '[object Object]': true, '[object RegExp]': true, '[object String]': true }"
|
||||||
|
);
|
||||||
|
|
||||||
// add brackets to whitelisted properties so Closure Compiler won't mung them
|
// add brackets to whitelisted properties so Closure Compiler won't mung them
|
||||||
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
|
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
|
||||||
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
|
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
|
||||||
|
|||||||
134
lodash.js
134
lodash.js
@@ -55,6 +55,9 @@
|
|||||||
reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
|
reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
|
||||||
reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
|
reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
|
||||||
|
|
||||||
|
/** Used to match regexp flags from their coerced string values */
|
||||||
|
var reFlags = /\w*$/;
|
||||||
|
|
||||||
/** Used to insert the data object variable into compiled template source */
|
/** Used to insert the data object variable into compiled template source */
|
||||||
var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g;
|
var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g;
|
||||||
|
|
||||||
@@ -110,6 +113,7 @@
|
|||||||
dateClass = '[object Date]',
|
dateClass = '[object Date]',
|
||||||
funcClass = '[object Function]',
|
funcClass = '[object Function]',
|
||||||
numberClass = '[object Number]',
|
numberClass = '[object Number]',
|
||||||
|
objectClass = '[object Object]',
|
||||||
regexpClass = '[object RegExp]',
|
regexpClass = '[object RegExp]',
|
||||||
stringClass = '[object String]';
|
stringClass = '[object String]';
|
||||||
|
|
||||||
@@ -159,6 +163,12 @@
|
|||||||
var arrayLikeClasses = {};
|
var arrayLikeClasses = {};
|
||||||
arrayLikeClasses[argsClass] = arrayLikeClasses[arrayClass] = arrayLikeClasses[stringClass] = true;
|
arrayLikeClasses[argsClass] = arrayLikeClasses[arrayClass] = arrayLikeClasses[stringClass] = true;
|
||||||
|
|
||||||
|
/** Used to identify object classifications that `_.clone` supports */
|
||||||
|
var cloneableClasses = {};
|
||||||
|
cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] =
|
||||||
|
cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] =
|
||||||
|
cloneableClasses[stringClass] = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to escape characters for inclusion in HTML.
|
* Used to escape characters for inclusion in HTML.
|
||||||
* The `>` and `/` characters don't require escaping in HTML and have no
|
* The `>` and `/` characters don't require escaping in HTML and have no
|
||||||
@@ -2464,23 +2474,129 @@
|
|||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a shallow clone of the `value`. Any nested objects or arrays will be
|
* Create a clone of `value`. If `deep` is `true`, all nested objects, excluding
|
||||||
* assigned by reference and not cloned.
|
* functons and `arguments` objects, will be cloned otherwise they will be
|
||||||
|
* assigned by reference.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @memberOf _
|
* @memberOf _
|
||||||
* @category Objects
|
* @category Objects
|
||||||
* @param {Mixed} value The value to clone.
|
* @param {Mixed} value The value to clone.
|
||||||
|
* @param {Boolean} deep A flag to indicate a deep clone.
|
||||||
|
* @param {Object} [guard] Internally used to allow this method to work with
|
||||||
|
* others like `_.map` without using their callback `index` argument for `deep`.
|
||||||
|
* @param {Array} [stack=[]] Internally used to keep track of traversed objects
|
||||||
|
* to avoid circular references.
|
||||||
* @returns {Mixed} Returns the cloned `value`.
|
* @returns {Mixed} Returns the cloned `value`.
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
|
* var stooges = [
|
||||||
|
* { 'name': 'moe', 'age': 40 },
|
||||||
|
* { 'name': 'larry', 'age': 50 },
|
||||||
|
* { 'name': 'curly', 'age': 60 }
|
||||||
|
* ];
|
||||||
|
*
|
||||||
* _.clone({ 'name': 'moe' });
|
* _.clone({ 'name': 'moe' });
|
||||||
* // => { 'name': 'moe' };
|
* // => { 'name': 'moe' }
|
||||||
|
*
|
||||||
|
* var shallow = _.clone(stooges);
|
||||||
|
* shallow[0] === stooges[0];
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* var deep = _.clone(stooges, true);
|
||||||
|
* shallow[0] === stooges[0];
|
||||||
|
* // => false
|
||||||
*/
|
*/
|
||||||
function clone(value) {
|
function clone(value, deep, guard, stack) {
|
||||||
return value && objectTypes[typeof value]
|
if (!value) {
|
||||||
? (isArray(value) ? value.slice() : extend({}, value))
|
return value;
|
||||||
: value;
|
}
|
||||||
|
var isObj = typeof value == 'object';
|
||||||
|
stack || (stack = []);
|
||||||
|
|
||||||
|
if (guard) {
|
||||||
|
deep = false;
|
||||||
|
}
|
||||||
|
// use custom `clone` method if available
|
||||||
|
if (value.clone && toString.call(value.clone) == funcClass) {
|
||||||
|
return value.clone(deep);
|
||||||
|
}
|
||||||
|
// inspect [[Class]]
|
||||||
|
if (isObj) {
|
||||||
|
var className = toString.call(value);
|
||||||
|
|
||||||
|
// don't clone `arguments` objects, functions, or non-object Objects
|
||||||
|
if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctor = value.constructor,
|
||||||
|
isArr = className == arrayClass,
|
||||||
|
useCtor = toString.call(ctor) == funcClass;
|
||||||
|
|
||||||
|
// IE < 9 presents nodes like `Object` objects:
|
||||||
|
// IE < 8 are missing the node's constructor property
|
||||||
|
// IE 8 node constructors are typeof "object"
|
||||||
|
// check if the constructor is `Object` as `Object instanceof Object` is `true`
|
||||||
|
if (className == objectClass &&
|
||||||
|
(isObj = useCtor && ctor instanceof ctor)) {
|
||||||
|
// An object's own properties are iterated before inherited properties.
|
||||||
|
// If the last iterated key belongs to an object's own property then
|
||||||
|
// there are no inherited enumerable properties.
|
||||||
|
forIn(value, function(objValue, objKey) { isObj = objKey; });
|
||||||
|
isObj = isObj == true || hasOwnProperty.call(value, isObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shallow clone
|
||||||
|
if (!isObj || !deep) {
|
||||||
|
// don't clone functions
|
||||||
|
return isObj
|
||||||
|
? (isArr ? slice.call(value) : extend({}, value))
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (className) {
|
||||||
|
case boolClass:
|
||||||
|
return new ctor(value == true);
|
||||||
|
|
||||||
|
case dateClass:
|
||||||
|
return new ctor(+value);
|
||||||
|
|
||||||
|
case numberClass:
|
||||||
|
case stringClass:
|
||||||
|
return new ctor(value);
|
||||||
|
|
||||||
|
case regexpClass:
|
||||||
|
return ctor(value.source, reFlags.exec(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for circular references and return corresponding clone
|
||||||
|
var length = stack.length;
|
||||||
|
while (length--) {
|
||||||
|
if (stack[length].value == value) {
|
||||||
|
return stack[length].clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init cloned object
|
||||||
|
length = value.length;
|
||||||
|
var result = isArr ? ctor(length) : (useCtor ? new ctor : {});
|
||||||
|
|
||||||
|
// add current clone and original value to the stack of traversed objects
|
||||||
|
stack.push({ 'clone': result, 'value': value });
|
||||||
|
|
||||||
|
// recursively populate clone (susceptible to call stack limits)
|
||||||
|
if (isArr) {
|
||||||
|
var index = -1;
|
||||||
|
while (++index < length) {
|
||||||
|
result[index] = clone(value[index], deep, null, stack);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
forOwn(value, function(objValue, key) {
|
||||||
|
result[key] = clone(objValue, deep, null, stack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2780,8 +2896,8 @@
|
|||||||
* @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 {Array} [stack] Internally used to keep track of "seen" objects to
|
* @param {Array} [stack=[]] Internally used to keep track of traversed objects
|
||||||
* avoid circular references.
|
* to avoid circular references.
|
||||||
* @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
|
* @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
|
|||||||
96
test/test.js
96
test/test.js
@@ -152,6 +152,102 @@
|
|||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
QUnit.module('lodash.clone');
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
function Klass() { }
|
||||||
|
Klass.prototype = { 'a': 1 };
|
||||||
|
|
||||||
|
var nonCloneable = {
|
||||||
|
'an arguments object': arguments,
|
||||||
|
'an element': window.document && document.body,
|
||||||
|
'a function': Klass,
|
||||||
|
'a Klass instance': new Klass
|
||||||
|
};
|
||||||
|
|
||||||
|
var objects = {
|
||||||
|
'an array': ['a', 'b', 'c', ''],
|
||||||
|
'an array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 },
|
||||||
|
'boolean': false,
|
||||||
|
'boolean object': Object(false),
|
||||||
|
'an object': { 'a': 0, 'b': 1, 'c': 3 },
|
||||||
|
'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
|
||||||
|
'null': null,
|
||||||
|
'a number': 3,
|
||||||
|
'a number object': Object(3),
|
||||||
|
'a regexp': /a/gim,
|
||||||
|
'a string': 'a',
|
||||||
|
'a string object': Object('a'),
|
||||||
|
'undefined': undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
objects['an array'].length = 5;
|
||||||
|
|
||||||
|
_.forOwn(objects, function(object, key) {
|
||||||
|
test('should deep clone ' + key + ' correctly', function() {
|
||||||
|
var clone = _.clone(object, true);
|
||||||
|
|
||||||
|
if (object == null) {
|
||||||
|
equal(clone, object);
|
||||||
|
} else {
|
||||||
|
deepEqual(clone.valueOf(), object.valueOf());
|
||||||
|
}
|
||||||
|
if (_.isObject(object)) {
|
||||||
|
ok(clone !== object);
|
||||||
|
} else {
|
||||||
|
skipTest();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_.forOwn(nonCloneable, function(object, key) {
|
||||||
|
test('should not clone ' + key, function() {
|
||||||
|
ok(_.clone(object) === object);
|
||||||
|
ok(_.clone(object, true) === object);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should shallow clone when used as `callback` for `_.map`', function() {
|
||||||
|
var expected = [{ 'a': [0] }, { 'b': [1] }],
|
||||||
|
actual = _.map(expected, _.clone);
|
||||||
|
|
||||||
|
ok(actual != expected && actual.a == expected.a && actual.b == expected.b);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should deep clone objects with circular references', function() {
|
||||||
|
var object = {
|
||||||
|
'foo': { 'b': { 'foo': { 'c': { } } } },
|
||||||
|
'bar': { }
|
||||||
|
};
|
||||||
|
|
||||||
|
object.foo.b.foo.c.foo = object;
|
||||||
|
object.bar.b = object.foo.b;
|
||||||
|
|
||||||
|
var clone = _.clone(object, true);
|
||||||
|
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && 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() {
|
||||||
|
deepEqual(_.clone(shadowed), shadowed);
|
||||||
|
deepEqual(_.clone(shadowed, true), shadowed);
|
||||||
|
});
|
||||||
|
}(1, 2, 3));
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
QUnit.module('lodash.contains');
|
QUnit.module('lodash.contains');
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user