什么是在JavaScript中深入克隆对象的最有效方法?
什么是克隆JavaScript对象最有效的方法? 我见过obj = eval(uneval(o));
正在使用,但这是非标准的,只有Firefox支持 。
我做了像obj = JSON.parse(JSON.stringify(o));
但质疑效率。
我也看到recursion复制function与各种缺陷。
我很惊讶没有规范的解决scheme存在。
注意:这是对另一个答案的回复,而不是对这个问题的正确答复。 如果你想快速克隆物体,请按照Corban的build议回答这个问题。
我想要注意的是, jQuery中的.clone()
方法只克隆DOM元素。 为了克隆JavaScript对象,你可以这样做:
// Shallow copy var newObject = jQuery.extend({}, oldObject); // Deep copy var newObject = jQuery.extend(true, {}, oldObject);
更多的信息可以在jQuery文档中find。
我还想指出,深层复制实际上比上面显示的要聪明得多 – 它能够避免很多陷阱(例如试图深度扩展DOM元素)。 它经常用在jQuery核心和插件中,效果很好。
结帐这个基准: http : //jsben.ch/#/bWfk9
在我以前的testing中,速度是我发现的主要问题
JSON.parse(JSON.stringify(obj))
是深入克隆一个对象的最快方法(它将深度标记设置为true的jQuery.extend跳出了10-20%)。
当深度标志设置为false(浅度克隆)时,jQuery.extend非常快。 这是一个不错的select,因为它包含了一些额外的typesvalidation逻辑,并且不会复制未定义的属性等,但是这也会让你稍微放慢一点。
如果您知道要克隆的对象的结构,或者可以避免深度嵌套的数组,您可以在检查hasOwnProperty的同时编写一个简单for (var i in obj)
循环来克隆对象,而且它比jQuery快得多。
最后,如果您试图在热循环中克隆已知的对象结构,只需简单地插入克隆过程并手动构build对象即可获得更多的性能。
JavaScript跟踪引擎吸引优化for..in
循环,检查hasOwnProperty也会减慢你的速度。 手动克隆速度是绝对必须的。
var clonedObject = { knownProp: obj.knownProp, .. }
注意使用Date
对象的JSON.parse(JSON.stringify(obj))
方法 – JSON.stringify(new Date())
返回ISO格式的date的string表示forms, JSON.parse()
不返回到Date
对象。 看到这个答案的更多细节 。
假设你只有variables而不是你的对象中的任何函数,你可以使用:
var newObject = JSON.parse(JSON.stringify(oldObject));
如果没有内build的,你可以尝试:
function clone(obj) { if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj) return obj; if (obj instanceof Date) var temp = new obj.constructor(); //or new Date(obj); else var temp = obj.constructor(); for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj['isActiveClone'] = null; temp[key] = clone(obj[key]); delete obj['isActiveClone']; } } return temp; }
结构克隆
HTML5定义了创build对象的深层克隆的方法。 它只适用于某些内置types,但比使用JSON要灵活得多。 内部结构化克隆algorithm还支持date,RegExps,地图,集合,Blob,FileLists,ImageDatas,稀疏数组和types化数组等不断增加的types ,并支持循环/recursion结构。
asynchronous克隆
没有专门用于创build结构化克隆的公共API,但通过MessageChannel发布消息是asynchronous克隆的一个非常直接的方法。 这里有一个可能的ES6 structuredClone(obj)
函数返回一个带有obj
结构化克隆的Promise
。
class StructuredCloner { constructor() { this.pendingClones_ = new Map(); this.nextKey_ = 0; const channel = new MessageChannel(); this.inPort_ = channel.port1; this.outPort_ = channel.port2; this.outPort_.onmessage = ({data: {key, value}}) => { const resolve = this.pendingClones_.get(key); resolve(value); this.pendingClones_.delete(key); }; this.outPort_.start(); } clone(value) { return new Promise(resolve => { const key = this.nextKey_++; this.pendingClones_.set(key, resolve); this.inPort_.postMessage({key, value}); }); } } const structuredClone = window.structuredClone = StructuredCloner.prototype.clone.bind(new StructuredCloner);
使用示例:
'use strict'; const main = () => { const original = { date: new Date(), number: Math.random() }; original.self = original; structuredClone(original).then(clone => { // They're different objects: console.assert(original !== clone); console.assert(original.date !== clone.date); // They're cyclical: console.assert(original.self === original); console.assert(clone.self === clone); // They contain equivalent values: console.assert(original.number === clone.number); console.assert(Number(original.date) === Number(clone.date)); console.log("Assertions complete."); }); }; class StructuredCloner { constructor() { this.pendingClones_ = new Map(); this.nextKey_ = 0; const channel = new MessageChannel(); this.inPort_ = channel.port1; this.outPort_ = channel.port2; this.outPort_.onmessage = ({data: {key, value}}) => { const resolve = this.pendingClones_.get(key); resolve(value); this.pendingClones_.delete(key); }; this.outPort_.start(); } clone(value) { return new Promise(resolve => { const key = this.nextKey_++; this.pendingClones_.set(key, resolve); this.inPort_.postMessage({key, value}); }); } } const structuredClone = window.structuredClone = StructuredCloner.prototype.clone.bind(new StructuredCloner); main();
在一行代码中克隆(而不是深度克隆)对象的有效方法
Object.assign
方法是ECMAScript 2015(ES6)标准的一部分,完全符合您的需求。
var clone = Object.assign({}, obj);
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。
阅读更多…
支持较旧浏览器的polyfill :
if (!Object.assign) { Object.defineProperty(Object, 'assign', { enumerable: false, configurable: true, writable: true, value: function(target) { 'use strict'; if (target === undefined || target === null) { throw new TypeError('Cannot convert first argument to object'); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var nextSource = arguments[i]; if (nextSource === undefined || nextSource === null) { continue; } nextSource = Object(nextSource); var keysArray = Object.keys(nextSource); for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { var nextKey = keysArray[nextIndex]; var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== undefined && desc.enumerable) { to[nextKey] = nextSource[nextKey]; } } } return to; } }); }
码:
// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned function extend(from, to) { if (from == null || typeof from != "object") return from; if (from.constructor != Object && from.constructor != Array) return from; if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function || from.constructor == String || from.constructor == Number || from.constructor == Boolean) return new from.constructor(from); to = to || new from.constructor(); for (var name in from) { to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name]; } return to; }
testing:
var obj = { date: new Date(), func: function(q) { return 1 + q; }, num: 123, text: "asdasd", array: [1, "asd"], regex: new RegExp(/aaa/i), subobj: { num: 234, text: "asdsaD" } } var clone = extend(obj);
这就是我正在使用的:
function cloneObject(obj) { var clone = {}; for(var i in obj) { if(typeof(obj[i])=="object" && obj[i] != null) clone[i] = cloneObject(obj[i]); else clone[i] = obj[i]; } return clone; }
var clone = function() { var newObj = (this instanceof Array) ? [] : {}; for (var i in this) { if (this[i] && typeof this[i] == "object") { newObj[i] = this[i].clone(); } else { newObj[i] = this[i]; } } return newObj; }; Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
性能深度复制:从最好到最差
- 重新分配“=”(string数组,仅数字数组)
- 切片(string数组,仅数字数组)
- 串联(string数组,数组 – 只)
- 自定义函数:for循环或recursion复制
- jQuery的$ .extend
- JSON.parse(string数组,数组数组,对象数组 – 仅)
- Underscore.js的_.clone(string数组,数组 – 只)
- Lo-Dash的_.cloneDeep
深层复制string或数字的数组(一级 – 无参考指针):
当一个数组包含数字和string – 函数像.slice(),.concat(),.splice(),赋值运算符“=”和Underscore.js的克隆函数; 将会对数组元素进行深层复制。
在哪里重新分配的performance最快:
var arr1 = ['a', 'b', 'c']; var arr2 = arr1; arr1 = ['a', 'b', 'c'];
.slice()比.concat()有更好的性能, http: //jsperf.com/duplicate-array-slice-vs-concat/3
var arr1 = ['a', 'b', 'c']; // Becomes arr1 = ['a', 'b', 'c'] var arr2a = arr1.slice(0); // Becomes arr2a = ['a', 'b', 'c'] - deep copy var arr2b = arr1.concat(); // Becomes arr2b = ['a', 'b', 'c'] - deep copy
深层复制对象数组(两个或更多级别 – 引用指针):
var arr1 = [{object:'a'}, {object:'b'}];
编写一个自定义函数(具有比$ .extend()或JSON.parse更快的性能):
function copy(o) { var out, v, key; out = Array.isArray(o) ? [] : {}; for (key in o) { v = o[key]; out[key] = (typeof v === "object") ? copy(v) : v; } return out; } copy(arr1);
使用第三方实用程序function:
$.extend(true, [], arr1); // Jquery Extend JSON.parse(arr1); _.cloneDeep(arr1); // Lo-dash
jQuery的$ .extend有更好的performance:
我知道这是一个旧的post,但我认为这可能对下一个绊倒的人有所帮助。
只要你不分配任何对象,它就不会在内存中引用。 所以要创build一个你想在其他对象之间共享的对象,你必须创build一个如下所示的工厂:
var a = function(){ return { father:'zacharias' }; }, b = a(), c = a(); c.father = 'johndoe'; alert(b.father);
有一个库(称为“克隆”) ,这很好。 它提供了我所知道的任意对象的最完整的recursion克隆/复制。 它也支持循环引用,但其他答案尚未涵盖。
你也可以在npm上find它 。 它可以用于浏览器以及Node.js.
这里是一个如何使用它的例子:
安装它
npm install clone
或者用Ender打包。
ender build clone [...]
您也可以手动下载源代码。
那么你可以在你的源代码中使用它。
var clone = require('clone'); var a = { foo: { bar: 'baz' } }; // inital value of a var b = clone(a); // clone a -> b a.foo.bar = 'foo'; // change a console.log(a); // { foo: { bar: 'foo' } } console.log(b); // { foo: { bar: 'baz' } }
(免责声明:我是图书馆的作者。)
如果你使用它, Underscore.js库有一个克隆方法。
var newObject = _.clone(oldObject);
Cloning
一个对象在JS中一直是个问题,但是在ES6之前,我只是列举了在下面的JavaScript中复制一个对象的不同方法,假设你有下面的对象,并且希望有一个深层次的副本:
var obj = {a:1, b:2, c:3, d:4};
有几种方法来复制这个对象,而不改变原点:
1)ES6,支持ES6的所有现代浏览器,但是如果你不使用浏览器,你可以使用BabelJs,在ES6中使用一个对象的深拷贝是很容易的:
var deepCopyObj = Object.assign({}, obj);
2)ES5 +,使用一个简单的function为你做副本:
function deepCopyObj(obj) { if (null == obj || "object" != typeof obj) return obj; if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } if (obj instanceof Array) { var copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = cloneSO(obj[i]); } return copy; } if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]); } return copy; } throw new Error("Unable to copy obj this object."); }
3)ES5 +,使用JSON.parse和JSON.stringify,不是一个推荐的方法,它很快,但以干净的代码方式,这不是最好的解决scheme:
var deepCopyObj = JSON.parse(JSON.stringify(obj));
4)AngularJs:
var deepCopyObj = angular.copy(obj);
5)jQuery:
var deepCopyObj = jQuery.extend(true, {}, obj);
6)UnderscoreJs&Loadash:
var deepCopyObj = _.clone(obj); //latest version UndescoreJs makes shallow copy
希望这些帮助…
以下创build同一个对象的两个实例。 我发现它,目前正在使用它。 它简单易用。
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Here's a version of ConroyP's answer above that works even if the constructor has required parameters:
//If Object.create isn't already defined, we just do the simple shim, //without the second argument, since that's all we need here var object_create = Object.create; if (typeof object_create !== 'function') { object_create = function(o) { function F() {} F.prototype = o; return new F(); }; } function deepCopy(obj) { if(obj == null || typeof(obj) !== 'object'){ return obj; } //make sure the returned object has the same prototype as the original var ret = object_create(obj.constructor.prototype); for(var key in obj){ ret[key] = deepCopy(obj[key]); } return ret; }
This function is also available in my simpleoo library.
编辑:
Here's a more robust version (thanks to Justin McCandless this now supports cyclic references as well):
/** * Deep copy an object (make copies of all its object properties, sub-properties, etc.) * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone * that doesn't break if the constructor has required parameters * * It also borrows some code from http://stackoverflow.com/a/11621004/560114 */ function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) { if(src === null || typeof(src) !== 'object'){ return src; } //Honor native/custom clone methods if(typeof src.clone == 'function'){ return src.clone(true); } //Special cases: //Date if(src instanceof Date){ return new Date(src.getTime()); } //RegExp if(src instanceof RegExp){ return new RegExp(src); } //DOM Element if(src.nodeType && typeof src.cloneNode == 'function'){ return src.cloneNode(true); } // Initialize the visited objects arrays if needed. // This is used to detect cyclic references. if (_visited === undefined){ _visited = []; _copiesVisited = []; } // Check if this object has already been visited var i, len = _visited.length; for (i = 0; i < len; i++) { // If so, get the copy we already made if (src === _visited[i]) { return _copiesVisited[i]; } } //Array if (Object.prototype.toString.call(src) == '[object Array]') { //[].slice() by itself would soft clone var ret = src.slice(); //add it to the visited array _visited.push(src); _copiesVisited.push(ret); var i = ret.length; while (i--) { ret[i] = deepCopy(ret[i], _visited, _copiesVisited); } return ret; } //If we've reached here, we have a regular object //make sure the returned object has the same prototype as the original var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__); if (!proto) { proto = src.constructor.prototype; //this line would probably only be reached by very old browsers } var dest = object_create(proto); //add this object to the visited array _visited.push(src); _copiesVisited.push(dest); for (var key in src) { //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc. //For an example of how this could be modified to do so, see the singleMixin() function dest[key] = deepCopy(src[key], _visited, _copiesVisited); } return dest; } //If Object.create isn't already defined, we just do the simple shim, //without the second argument, since that's all we need here var object_create = Object.create; if (typeof object_create !== 'function') { object_create = function(o) { function F() {} F.prototype = o; return new F(); }; }
function clone(obj) { var clone = {}; clone.prototype = obj.prototype; for (property in obj) clone[property] = obj[property]; return clone; }
Crockford suggests (and I prefer) using this function:
function object(o) { function F() {} F.prototype = o; return new F(); } var newObject = object(oldObject);
It's terse, works as expected and you don't need a library.
编辑:
This is a polyfill for Object.create
, so you also can use this.
var newObject = Object.create(oldObject);
NOTE: If you use some of this, you may have problems with some iteration who use hasOwnProperty
. Because, create
create new empty object who inherits oldObject
. But it is still useful and practical for cloning objects.
For exemple if oldObject.a = 5;
newObject.a; // is 5
但:
oldObject.hasOwnProperty(a); // is true newObject.hasOwnProperty(a); // is false
Shallow copy one-liner ( ECMAScript 5th edition ):
var origin = { foo : {} }; var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{}); console.log(origin, copy); console.log(origin == copy); // false console.log(origin.foo == copy.foo); // true
And shallow copy one-liner ( ECMAScript 6th edition , 2015):
var origin = { foo : {} }; var copy = Object.assign({}, origin); console.log(origin, copy); console.log(origin == copy); // false console.log(origin.foo == copy.foo); // true
There seems to be no ideal deep clone operator yet for array-like objects. As the code below illustrates, John Resig's jQuery cloner turns arrays with non-numeric properties into objects that are not arrays, and RegDwight's JSON cloner drops the non-numeric properties. The following tests illustrate these points on multiple browsers:
function jQueryClone(obj) { return jQuery.extend(true, {}, obj) } function JSONClone(obj) { return JSON.parse(JSON.stringify(obj)) } var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]]; arrayLikeObj.names = ["m", "n", "o"]; var JSONCopy = JSONClone(arrayLikeObj); var jQueryCopy = jQueryClone(arrayLikeObj); alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) + "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) + "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names + "\nAnd what are the JSONClone names? " + JSONCopy.names)
Lodash has a nice _.cloneDeep method: http://lodash.com/docs#cloneDeep
The usual _.clone method also accepts a second parameter to make a deep copy instead of the shallow one: http://lodash.com/docs#clone
_.clone(value [, deep=false, callback, thisArg])
I have two good answers depending on whether your objective is to clone a "plain old JavaScript object" or not.
Let's also assume that your intention is to create a complete clone with no prototype references back to the source object. If you're not interested in a complete clone, then you can use many of the Object.clone() routines provided in some of the other answers (Crockford's pattern).
For plain old JavaScript objects, a tried and true good way to clone an object in modern runtimes is quite simply:
var clone = JSON.parse(JSON.stringify(obj));
Note that the source object must be a pure JSON object. This is to say, all of its nested properties must be scalars (like boolean, string, array, object, etc). Any functions or special objects like RegExp or Date will not be cloned.
Is it efficient? Heck yes. We've tried all kinds of cloning methods and this works best. I'm sure some ninja could conjure up a faster method. But I suspect we're talking about marginal gains.
This approach is just simple and easy to implement. Wrap it into a convenience function and if you really need to squeeze out some gain, go for at a later time.
Now, for non-plain JavaScript objects, there isn't a really simple answer. In fact, there can't be because of the dynamic nature of JavaScript functions and inner object state. Deep cloning a JSON structure with functions inside requires you recreate those functions and their inner context. And JavaScript simply doesn't have a standardized way of doing that.
The correct way to do this, once again, is via a convenience method that you declare and reuse within your code. The convenience method can be endowed with some understanding of your own objects so you can make sure to properly recreate the graph within the new object.
We're written our own, but the best general approach I've seen is covered here:
http://davidwalsh.name/javascript-clone
This is the right idea. The author (David Walsh) has commented out the cloning of generalized functions. This is something you might choose to do, depending on your use case.
The main idea is that you need to special handle the instantiation of your functions (or prototypal classes, so to speak) on a per-type basis. Here, he's provided a few examples for RegExp and Date.
Not only is this code brief, but it's also very readable. It's pretty easy to extend.
Is this efficient? Heck yes. Given that the goal is to produce a true deep-copy clone, then you're going to have to walk the members of the source object graph. With this approach, you can tweak exactly which child members to treat and how to manually handle custom types.
所以你去了 Two approaches. Both are efficient in my view.
Just because I didn't see AngularJS mentioned and thought that people might want to know…
angular.copy
also provides a method of deep copying objects and arrays.
This isn't generally the most efficient solution, but it does what I need. Simple test cases below…
function clone(obj, clones) { // Makes a deep copy of 'obj'. Handles cyclic structures by // tracking cloned obj's in the 'clones' parameter. Functions // are included, but not cloned. Functions members are cloned. var new_obj, already_cloned, t = typeof obj, i = 0, l, pair; clones = clones || []; if (obj === null) { return obj; } if (t === "object" || t === "function") { // check to see if we've already cloned obj for (i = 0, l = clones.length; i < l; i++) { pair = clones[i]; if (pair[0] === obj) { already_cloned = pair[1]; break; } } if (already_cloned) { return already_cloned; } else { if (t === "object") { // create new object new_obj = new obj.constructor(); } else { // Just use functions as is new_obj = obj; } clones.push([obj, new_obj]); // keep track of objects we've cloned for (key in obj) { // clone object members if (obj.hasOwnProperty(key)) { new_obj[key] = clone(obj[key], clones); } } } } return new_obj || obj; }
Cyclic array test…
a = [] a.push("b", "c", a) aa = clone(a) aa === a //=> false aa[2] === a //=> false aa[2] === a[2] //=> false aa[2] === aa //=> true
Function test…
f = new Function fa = a ff = clone(f) ff === f //=> true ff.a === a //=> false
AngularJS
Well if you're using angular you could do this too
var newObject = angular.copy(oldObject);
For future reference, the current draft of ECMAScript 6 introduces Object.assign as a way of cloning objects. Example code would be:
var obj1 = { a: true, b: 1 }; var obj2 = Object.assign(obj1); console.log(obj2); // { a: true, b: 1 }
At the time of writing support is limited to Firefox 34 in browsers so it's not usable in production code just yet (unless you're writing a Firefox extension of course).
Cloning an object using today's JavaScript: ECMAScript 2015 (formerly known as ECMAScript 6)
var original = {a: 1}; // Method 1: New object with original assigned. var copy1 = Object.assign({}, original); // Method 2: New object with spread operator assignment. var copy2 = {...original};
Old browsers may not support ECMAScript 2015. A common solution is to use a JavaScript-to-JavaScript compiler like Babel to output an ECMAScript 5 version of your JavaScript code.
As pointed out by @jim-hall , this is only a shallow copy . Properties of properties are copied as a reference: changing one would change the value in the other object/instance.
I disagree with the answer with the greatest votes here . A Recursive Deep Clone is much faster than the JSON.parse(JSON.stringify(obj)) approach mentioned.
- Jsperf ranks it number one here: https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
- Jsben from the answer above updated to show that a recursive deep clone beats all the others mentioned: http://jsben.ch/13YKQ
And here's the function for quick reference:
function cloneDeep (o) { let newO let i if (typeof o !== 'object') return o if (!o) return o if (Object.prototype.toString.apply(o) === '[object Array]') { newO = [] for (i = 0; i < o.length; i += 1) { newO[i] = cloneDeep(o[i]) } return newO } newO = {} for (i in o) { if (o.hasOwnProperty(i)) { newO[i] = cloneDeep(o[i]) } } return newO }
// obj target object, vals source object
var setVals = function (obj, vals) { if (obj && vals) { for (var x in vals) { if (vals.hasOwnProperty(x)) { if (obj[x] && typeof vals[x] === 'object') { obj[x] = setVals(obj[x], vals[x]); } else { obj[x] = vals[x]; } } } } return obj; };
I usually use var newObj = JSON.parse( JSON.stringify(oldObje) );
but, here's a more proper way:
var o = {}; var oo = Object.create(o); (o === oo); // => false
Watch legacy browsers!