// Create an inner object with a variable x whose default// value is 3.function innerObj(){this.x = 3;}innerObj.prototype.clone = function() {var temp = new innerObj();for (myvar in this) {// this object does not contain any objects, so// use the lightweight copy code.temp[myvar] = this[myvar];}return temp;}
// Create an outer object with a variable y whose default// value is 77.function outerObj(){// The outer object contains an inner object. Allocate it here.this.inner = new innerObj();this.y = 77;}outerObj.prototype.clone = function() {var temp = new outerObj();for (myvar in this) {if (this[myvar].clone) {// This variable contains an object with a// clone operator. Call it to create a copy.temp[myvar] = this[myvar].clone();} else {// This variable contains a scalar value,// a string value, or an object with no// clone function. Assign it directly.temp[myvar] = this[myvar];}}return temp;}
// Allocate an outer object and assign non-default values to variables in// both the outer and inner objects.outer = new outerObj;outer.inner.x = 4;outer.y = 16;
// Clone the outer object (which, in turn, clones the inner object).newouter = outer.clone();
// Verify that both values were copied.alert('inner x is '+newouter.inner.x); // prints 4alert('y is '+newouter.y); // prints 16
// This would be cloneable:var tree = {"left" : { "left" : null, "right" : null, "data" : 3 },"right" : null,"data" : 8};
// This would kind-of work, but you would get 2 copies of the// inner node instead of 2 references to the same copyvar directedAcylicGraph = {"left" : { "left" : null, "right" : null, "data" : 3 },"data" : 8};directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
// Cloning this would cause a stack overflow due to infinite recursion:var cyclicGraph = {"left" : { "left" : null, "right" : null, "data" : 3 },"data" : 8};cyclicGraph["right"] = cyclicGraph;
var foo = { a : 1 };var bar = Object.create(foo);foo.a; // 1bar.a; // 1foo.a = 2;bar.a; // 2 - prototype changedbar.a = 3;foo.a; // Still 2, since setting bar.a makes it an "own" property
var obj1 = { text: 'moo1' };var obj2 = Object.create(obj1); // Creates a new clone without references
obj2.text = 'moo2'; // Only updates obj2's text property
console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}
对于当前不支持Object.create的浏览器/引擎,您可以使用此Poly填充:
// Polyfill Object.create if it does not existif (!Object.create) {Object.create = function (o) {var F = function () {};F.prototype = o;return new F();};}
// Returns true, if one of the parent's children is the target.// This is useful, for avoiding copyObj() through an infinite loop!function isChild(target, parent) {if (toType(parent) == '[object Object]') {for (var name in parent) {var curProperty = parent[name];
// Direct child.if (curProperty = target) return true;
// Check if target is a child of this property, and so on, recursively.if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {if (isChild(target, curProperty)) return true;}}} else if (toType(parent) == '[object Array]') {for (var i=0; i < parent.length; i++) {var curItem = parent[i];
// Direct child.if (curItem = target) return true;
// Check if target is a child of this property, and so on, recursively.if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {if (isChild(target, curItem)) return true;}}}
return false; // Not the target.}
/*a function for deep cloning objects that contains other nested objects and circular structures.objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.index (z)|||||| depth (x)|_ _ _ _ _ _ _ _ _ _ _ _/_/_/_/_/_/_/_/_/_//_/_/_/_/_/_/_/_/_//_/_/_/_/_/_/.....//.................//..... // //------------------object length (y) /*/
以下是执行情况:
function deepClone(obj) {var depth = -1;var arr = [];return clone(obj, arr, depth);}
/**** @param obj source object* @param arr 3D array to store the references to objects* @param depth depth of the current object relative to the passed 'obj'* @returns {*}*/function clone(obj, arr, depth){if (typeof obj !== "object") {return obj;}
var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'
var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original objectif(result instanceof Array){result.length = length;}
depth++; // depth is increased because we entered an object here
arr[depth] = []; // this is the x-axis, each index here is the deptharr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)// start the depth at current and go down, cyclic structures won't form on depths more than the current onefor(var x = depth; x >= 0; x--){// loop only if the array at this depth and length already have elementsif(arr[x][length]){for(var index = 0; index < arr[x][length].length; index++){if(obj === arr[x][length][index]){return obj;}}}}
arr[depth][length].push(obj); // store the object in the array at the current depth and lengthfor (var prop in obj) {if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);}
return result;}
function isFunction(functionToCheck) {var getType = {};return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';}
function cloneObjectByTemplate(obj, tpl, cloneConstructor) {if (typeof cloneConstructor === "undefined") {cloneConstructor = false;}if (obj == null || typeof (obj) != 'object') return obj;
//if we have an array, work through it's contents and apply the template to each item...if (Array.isArray(obj)) {var ret = [];for (var i = 0; i < obj.length; i++) {ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));}return ret;}
//otherwise we have an object...//var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.var temp = cloneConstructor ? new obj.constructor() : {};
for (var key in tpl) {//if we are provided with a function to determine the value of this property, call it...if (isFunction(tpl[key])) {temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value} else {//if our object has this property...if (obj[key] != undefined) {if (Array.isArray(obj[key])) {temp[key] = [];for (var i = 0; i < obj[key].length; i++) {temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));}} else {temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);}}}}
return temp;}
调用它的简单方法是这样的:
var source = {a: "whatever",b: {x: "yeah",y: "haha"}};var template = {a: true, //we want to clone "a"b: {x: true //we want to clone "b.x" too}};var destination = cloneObjectByTemplate(source, template);
var template = {tags: function (srcObj) {var tags = [];if (process.tags != undefined) {for (var i = 0; i < process.tags.length; i++) {
tags.push(cloneObjectByTemplate(srcObj.tags[i],{ a : true, b : true } //another template for each item in the array);}}return tags;}}
function deepCopy(object) {const cache = new WeakMap(); // Map of old - new references
function copy(obj) {if (typeof obj !== 'object' ||obj === null ||obj instanceof HTMLElement)return obj; // primitive value or HTMLElement
if (obj instanceof Date)return new Date().setTime(obj.getTime());
if (obj instanceof RegExp)return new RegExp(obj.source, obj.flags);
if (cache.has(obj))return cache.get(obj);
const result = obj instanceof Array ? [] : {};
cache.set(obj, result); // store reference to object before the recursive starts
if (obj instanceof Array) {for(const o of obj) {result.push(copy(o));}return result;}
const keys = Object.keys(obj);
for (const key of keys)result[key] = copy(obj[key]);
return result;}
return copy(object);}
一些测试:
// #1const obj1 = { };const obj2 = { };obj1.obj2 = obj2;obj2.obj1 = obj1; // Trivial circular reference
var copy = deepCopy(obj1);copy == obj1 // falsecopy.obj2 === obj1.obj2 // falsecopy.obj2.obj1.obj2 // and so on - no error (correctly cloned).
// #2const obj = { x: 0 }const clone = deepCopy({ a: obj, b: obj });clone.a == clone.b // true
// #3const arr = [];arr[0] = arr; // A little bit weird but who caresclone = deepCopy(arr)clone == arr // false;clone[0][0][0][0] == clone // true;
注意:我正在使用常量、for of loop、=>运算符和WeakMaps来创建更重要的代码。今天的浏览器支持这种语法(ES6)
> a = { a: 'A', b: 'B', c: 'C', d: 'D' }{ a: 'A', b: 'B', c: 'C', d: 'D' }> b = copy( a ){ a: 'A', b: 'B', c: 'C', d: 'D' }> a = [1,2,3,4][ 1, 2, 3, 4 ]> b = copy( a )[ 1, 2, 3, 4 ]
并且由于均值get/setter、构造函数所需的参数或只读属性以及对父亲的罪恶而失败。
> a = /\w+/g/\w+/g> b = copy( a ) // fails because source and flags are read-only/(?:)/> a = new Date ( '1/1/2001' )2000-12-31T16:00:00.000Z> b = copy( a ) // fails because Date using methods to get and set things2017-02-04T14:44:13.990Z> a = new Boolean( true )[Boolean: true]> b = copy( a ) // fails because of of sins against the father[Boolean: false]> a = new Number( 37 )[Number: 37]> b = copy( a ) // fails because of of sins against the father[Number: 0]> a = new String( 'four score and seven years ago our four fathers' )[String: 'four score and seven years ago our four fathers']> b = copy( a ) // fails because of of sins against the father{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' }
/** Creates a new object that combines the properties of the specified objects. */function combine(...objs: {}[]) {const combined = {};objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));return combined;}
function A(obj) {return {...obj}}
function B(obj) {return Object.assign({}, obj);}
function C(obj) {return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {})}
function D(obj) {let copyOfObject = {};Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));return copyOfObject;}
function E(obj) {return jQuery.extend({}, obj) // shallow}
function F(obj) {return _.clone(obj);}
function G(obj) {return _.clone(obj,true);}
function H(obj) {return _.extend({},obj);}
function I(obj) {if (null == obj || "object" != typeof obj) return obj;var copy = obj.constructor();for (var attr in obj) {if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];}return copy;}
function J(obj) {return _.cloneDeep(obj,true);}
function K(obj) {return JSON.parse(JSON.stringify(obj));}
function L(obj) {return jQuery.extend(true, {}, obj) // deep}
function M(obj) {if(obj == null || typeof(obj) != 'object')return obj;var temp = new obj.constructor();for(var key in obj)temp[key] = M(obj[key]);return temp;}
function N(obj) {let EClone = function(obj) {var newObj = (obj instanceof Array) ? [] : {};for (var i in obj) {if (i == 'EClone') continue;if (obj[i] && typeof obj[i] == "object") {newObj[i] = EClone(obj[i]);} else newObj[i] = obj[i]} return newObj;};
return EClone(obj);};
function O(obj) {if (obj == null || typeof obj != "object") return obj;if (obj.constructor != Object && obj.constructor != Array) return obj;if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)return new obj.constructor(obj);
let to = new obj.constructor();
for (var name in obj){to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name];}
return to;}
function P(obj) {function clone(target, source){
for(let key in source){
// Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.let descriptor = Object.getOwnPropertyDescriptor(source, key);if(descriptor.value instanceof String){target[key] = new String(descriptor.value);}else if(descriptor.value instanceof Array){target[key] = clone([], descriptor.value);}else if(descriptor.value instanceof Object){let prototype = Reflect.getPrototypeOf(descriptor.value);let cloneObject = clone({}, descriptor.value);Reflect.setPrototypeOf(cloneObject, prototype);target[key] = cloneObject;}else {Object.defineProperty(target, key, descriptor);}}let prototype = Reflect.getPrototypeOf(source);Reflect.setPrototypeOf(target, prototype);return target;}return clone({},obj);}
function Q(obj) {var copy;
// Handle the 3 simple types, and null or undefinedif (null == obj || "object" != typeof obj) return obj;
// Handle Dateif (obj instanceof Date) {copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {copy = [];for (var i = 0, len = obj.length; i < len; i++) {copy[i] = Q(obj[i]);}return copy;}
// Handle Objectif (obj instanceof Object) {copy = {};for (var attr in obj) {if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]);}return copy;}
throw new Error("Unable to copy obj! Its type isn't supported.");}
function R(obj) {const gdcc = "__getDeepCircularCopy__";if (obj !== Object(obj)) {return obj; // primitive value}
var set = gdcc in obj,cache = obj[gdcc],result;if (set && typeof cache == "function") {return cache();}// elseobj[gdcc] = function() { return result; }; // overwriteif (obj instanceof Array) {result = [];for (var i=0; i<obj.length; i++) {result[i] = R(obj[i]);}} else {result = {};for (var prop in obj)if (prop != gdcc)result[prop] = R(obj[prop]);else if (set)result[prop] = R(cache);}if (set) {obj[gdcc] = cache; // reset} else {delete obj[gdcc]; // unset again}return result;}
function S(obj) {const cache = new WeakMap(); // Map of old - new references
function copy(object) {if (typeof object !== 'object' ||object === null ||object instanceof HTMLElement)return object; // primitive value or HTMLElement
if (object instanceof Date)return new Date().setTime(object.getTime());
if (object instanceof RegExp)return new RegExp(object.source, object.flags);
if (cache.has(object))return cache.get(object);
const result = object instanceof Array ? [] : {};
cache.set(object, result); // store reference to object before the recursive starts
if (object instanceof Array) {for(const o of object) {result.push(copy(o));}return result;}
const keys = Object.keys(object);
for (const key of keys)result[key] = copy(object[key]);
return result;}
return copy(obj);}
function T(obj){var clonedObjectsArray = [];var originalObjectsArray = []; //used to remove the unique ids when finishedvar next_objid = 0;
function objectId(obj) {if (obj == null) return null;if (obj.__obj_id == undefined){obj.__obj_id = next_objid++;originalObjectsArray[obj.__obj_id] = obj;}return obj.__obj_id;}
function cloneRecursive(obj) {if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;
// Handle Dateif (obj instanceof Date) {var copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {var copy = [];for (var i = 0; i < obj.length; ++i) {copy[i] = cloneRecursive(obj[i]);}return copy;}
// Handle Objectif (obj instanceof Object) {if (clonedObjectsArray[objectId(obj)] != undefined)return clonedObjectsArray[objectId(obj)];
var copy;if (obj instanceof Function)//Handle Functioncopy = function(){return obj.apply(this, arguments);};elsecopy = {};
clonedObjectsArray[objectId(obj)] = copy;
for (var attr in obj)if (attr != "__obj_id" && obj.hasOwnProperty(attr))copy[attr] = cloneRecursive(obj[attr]);
return copy;}
throw new Error("Unable to copy obj! Its type isn't supported.");}var cloneObj = cloneRecursive(obj);
//remove the unique idsfor (var i = 0; i < originalObjectsArray.length; i++){delete originalObjectsArray[i].__obj_id;};
return cloneObj;}
function U(obj) {/*Deep copy objects by value rather than by reference,exception: `Proxy`*/
const seen = new WeakMap()
return clone(obj)
function defineProp(object, key, descriptor = {}, copyFrom = {}) {const { configurable: _configurable, writable: _writable }= Object.getOwnPropertyDescriptor(object, key)|| { configurable: true, writable: true }
const test = _configurable // Can redefine property&& (_writable === undefined || _writable) // Can assign to property
if (!test || arguments.length <= 2) return test
const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)|| { configurable: true, writable: true } // Custom…|| {}; // …or left to native default settings
["get", "set", "value", "writable", "enumerable", "configurable"].forEach(attr =>descriptor[attr] === undefined &&(descriptor[attr] = basisDesc[attr]))
const { get, set, value, writable, enumerable, configurable }= descriptor
return Object.defineProperty(object, key, {enumerable, configurable, ...get || set? { get, set } // Accessor descriptor: { value, writable } // Data descriptor})}
function clone(object) {if (object !== Object(object)) return object /*—— Check if the object belongs to a primitive data type */
if (object instanceof Node) return object.cloneNode(true) /*—— Clone DOM trees */
let _object // The clone of object
switch (object.constructor) {case Array:case Object:_object = cloneObject(object)break
case Date:_object = new Date(+object)break
case Function:const fnStr = String(object)
_object = new Function("return " +(/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)? "function " : "") + fnStr)()
copyPropDescs(_object, object)break
case RegExp:_object = new RegExp(object)break
default:switch (Object.prototype.toString.call(object.constructor)) {// // Stem from:case "[object Function]": // `class`case "[object Undefined]": // `Object.create(null)`_object = cloneObject(object)break
default: // `Proxy`_object = object}}
return _object}
function cloneObject(object) {if (seen.has(object)) return seen.get(object) /*—— Handle recursive references (circular structures) */
const _object = Array.isArray(object)? []: Object.create(Object.getPrototypeOf(object)) /*—— Assign [[Prototype]] for inheritance */
seen.set(object, _object) /*—— Make `_object` the associative mirror of `object` */
Reflect.ownKeys(object).forEach(key =>defineProp(_object, key, { value: clone(object[key]) }, object))
return _object}
function copyPropDescs(target, source) {Object.defineProperties(target,Object.getOwnPropertyDescriptors(source))}}
// ------------------------// Test properties// ------------------------
console.log(` shallow deep func circ undefined date RegExp bigInt`)
log(A);log(B);log(C);log(D);log(E);log(F);log(G);log(H);log(I);log(J);log(K);log(L);log(M);log(N);log(O);log(P);log(Q);log(R);log(S);log(T);log(U);
console.log(` shallow deep func circ undefined date RegExp bigInt----LEGEND:shallow - solution create shallow copydeep - solution create deep copyfunc - solution copy functionscirc - solution can copy object with circular referencesundefined - solution copy fields with undefined valuedate - solution can copy dateRegExp - solution can copy fields with regular expressionsbigInt - solution can copy BigInt`)
// ------------------------// Helper functions// ------------------------
function deepCompare(obj1,obj2) {return JSON.stringify(obj1)===JSON.stringify(obj2);}
function getCase() { // pure data casereturn {undef: undefined,bool: true, num: 1, str: "txt1",e1: null, e2: [], e3: {}, e4: 0, e5: false,arr: [ false, 2, "txt3", null, [], {},[ true,4,"txt5",null, [], {}, [true,6,"txt7",null,[],{} ],{bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}],{bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}],obj: {bool: true, num: 12, str: "txt13",e1: null, e2: [], e3: {}, e4: 0, e5: false,arr: [true,14,"txt15",null,[],{} ],obj: {bool: true, num: 16, str: "txt17",e1: null, e2: [], e3: {}, e4: 0, e5: false,arr: [true,18,"txt19",null,[],{} ],obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}}}};}
function check(org, copy, field, newValue) {copy[field] = newValue;return deepCompare(org,copy);}
function testFunc(f) {let o = { a:1, fun: (i,j)=> i+j };let c = f(o);
let val = falsetry{val = c.fun(3,4)==7;} catch(e) { }return val;}
function testCirc(f) {function Circ() {this.me = this;}
var o = {x: 'a',circ: new Circ(),obj_circ: null,};
o.obj_circ = o;
let val = false;
try{let c = f(o);val = (o.obj_circ == o) && (o.circ == o.circ.me);} catch(e) { }return val;}
function testRegExp(f) {let o = {re: /a[0-9]+/,};
let val = false;
try{let c = f(o);val = (String(c.re) == String(/a[0-9]+/));} catch(e) { }return val;}
function testDate(f) {let o = {date: new Date(),};
let val = false;
try{let c = f(o);val = (+new Date(c.date) == +new Date(o.date));} catch(e) { }return val;}
function testBigInt(f) {let val = false;
try{let o = {big: 123n,};
let c = f(o);
val = o.big == c.big;} catch(e) { }
return val;}
function log(f) {let o = getCase(); // orginal objectlet oB = getCase(); // "backup" used for shallow valid test
let c1 = f(o); // copy 1 for referencelet c2 = f(o); // copy 2 for test shallow valueslet c3 = f(o); // copy 3 for test deep values
let is_proper_copy = deepCompare(c1,o); // shoud be true
// shallow changeslet testShallow =[ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ].reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
// should be true (original object shoud not have changed shallow fields)let is_valid = deepCompare(o,oB);
// deep test (intruduce some change)if (c3.arr[6]) c3.arr[6][7].num = 777;
let diff_shallow = !testShallow; // shoud be true (shallow field was copied)let diff_deep = !deepCompare(c1,c3); // shoud be true (deep field was copied)let can_copy_functions = testFunc(f);let can_copy_circular = testCirc(f);let can_copy_regexp = testRegExp(f);let can_copy_date = testDate(f);let can_copy_bigInt = testBigInt(f);
let has_undefined = 'undef' in c1; // field with undefined value is copied?let is_ok = is_valid && is_proper_copy;let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
testFunc(f);
if(is_ok) {console.log(`${f.name} ${b(diff_shallow)} ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)} ${b(can_copy_date)} ${b(can_copy_regexp)} ${b(can_copy_bigInt)}`)} else {console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1})}
}
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
This snippet only presents tested solutions and show differences between them (but it no make performence tests)
let objA = {a: "keyA",b: {c: "keyC",}}let objB = Object.assign({}, objA); // or {...objB}// change objBobjB.a = "Change objA.a (top)"console.log("objA.a (top) No Change:\n" + JSON.stringify(objA, false, 2));
objB.b.c = "change should be only for objB.b.c but it in objA.b.c"console.log("objA.a.c second level has Change:\n" + JSON.stringify(objA, false, 2));
let objA = {a: "keyA",b: {c: "keyC",}}let objB = typeof structuredClone == 'function' ?structuredClone(objA) : JSON.parse(JSON.stringify(objA));// change objBobjB.a = "Change objA.a (top)"objB.b.c = "change should be only for objB.c but it in objA.c"
console.log("objA has no Change:\n" + JSON.stringify(objA, false, 2));