mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-02 16:17: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': [],
|
||||
'bindAll': ['bind', 'functions'],
|
||||
'chain': ['mixin'],
|
||||
'clone': ['extend', 'isArray'],
|
||||
'clone': ['extend', 'forOwn', 'isArguments'],
|
||||
'compact': [],
|
||||
'compose': [],
|
||||
'contains': [],
|
||||
@@ -774,17 +774,12 @@
|
||||
|
||||
// build replacement code
|
||||
lodash.forOwn({
|
||||
'Arguments': 'argsClass',
|
||||
'Date': 'dateClass',
|
||||
'Function': 'funcClass',
|
||||
'Number': 'numberClass',
|
||||
'RegExp': 'regexpClass',
|
||||
'String': 'stringClass'
|
||||
}, function(value, key) {
|
||||
// skip `isArguments` if not a mobile build
|
||||
if (!isMobile && key == 'Arguments') {
|
||||
return;
|
||||
}
|
||||
var funcName = 'is' + key,
|
||||
funcCode = matchFunction(source, funcName);
|
||||
|
||||
@@ -887,15 +882,19 @@
|
||||
// remove IE `shift` and `splice` fix from mutator Array functions mixin
|
||||
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, '');
|
||||
|
||||
// remove `noCharByIndex` from `_.reduceRight`
|
||||
source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
|
||||
// remove `noArgsClass` from `_.clone` and `_.size`
|
||||
source = source.replace(/ *\|\| *\(noArgsClass *&[^)]+?\)\)/g, '');
|
||||
|
||||
// remove `noArraySliceOnStrings` from `_.toArray`
|
||||
source = source.replace(/noArraySliceOnStrings *\?[^:]+: *([^)]+)/g, '$1');
|
||||
|
||||
// remove `noCharByIndex` from `_.reduceRight`
|
||||
source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
|
||||
|
||||
source = removeVar(source, 'extendIteratorOptions');
|
||||
source = removeVar(source, 'hasDontEnumBug');
|
||||
source = removeVar(source, 'iteratorTemplate');
|
||||
source = removeVar(source, 'noArgsClass');
|
||||
source = removeVar(source, 'noArraySliceOnStrings');
|
||||
source = removeVar(source, 'noCharByIndex');
|
||||
source = removeIsArgumentsFallback(source);
|
||||
|
||||
@@ -220,6 +220,13 @@
|
||||
// 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 }");
|
||||
|
||||
// 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
|
||||
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
|
||||
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
|
||||
|
||||
134
lodash.js
134
lodash.js
@@ -55,6 +55,9 @@
|
||||
reEmptyStringMiddle = /\b(__p \+=) '' \+/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 */
|
||||
var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g;
|
||||
|
||||
@@ -110,6 +113,7 @@
|
||||
dateClass = '[object Date]',
|
||||
funcClass = '[object Function]',
|
||||
numberClass = '[object Number]',
|
||||
objectClass = '[object Object]',
|
||||
regexpClass = '[object RegExp]',
|
||||
stringClass = '[object String]';
|
||||
|
||||
@@ -159,6 +163,12 @@
|
||||
var arrayLikeClasses = {};
|
||||
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.
|
||||
* 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
|
||||
* assigned by reference and not cloned.
|
||||
* Create a clone of `value`. If `deep` is `true`, all nested objects, excluding
|
||||
* functons and `arguments` objects, will be cloned otherwise they will be
|
||||
* assigned by reference.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @category Objects
|
||||
* @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`.
|
||||
* @example
|
||||
*
|
||||
* var stooges = [
|
||||
* { 'name': 'moe', 'age': 40 },
|
||||
* { 'name': 'larry', 'age': 50 },
|
||||
* { 'name': 'curly', 'age': 60 }
|
||||
* ];
|
||||
*
|
||||
* _.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) {
|
||||
return value && objectTypes[typeof value]
|
||||
? (isArray(value) ? value.slice() : extend({}, value))
|
||||
: value;
|
||||
function clone(value, deep, guard, stack) {
|
||||
if (!value) {
|
||||
return 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
|
||||
* @param {Mixed} a The value to compare.
|
||||
* @param {Mixed} b The other value to compare.
|
||||
* @param {Array} [stack] Internally used to keep track of "seen" objects to
|
||||
* avoid circular references.
|
||||
* @param {Array} [stack=[]] Internally used to keep track of traversed objects
|
||||
* to avoid circular references.
|
||||
* @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
|
||||
* @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');
|
||||
|
||||
(function() {
|
||||
|
||||
Reference in New Issue
Block a user