提问人: 提问时间:9/24/2008 最后编辑:26 revs, 21 users 25%jschrab 更新时间:4/5/2023 访问量:2856261
在 JavaScript 中深度克隆对象的最有效方法是什么?
What is the most efficient way to deep clone an object in JavaScript?
问:
克隆 JavaScript 对象的最有效方法是什么?我见过被使用,但这是非标准的,只有Firefox支持。
我做过这样的事情,但质疑效率。
我还看到了具有各种缺陷的递归复制函数。
我很惊讶没有规范的解决方案存在。obj = eval(uneval(o));
obj = JSON.parse(JSON.stringify(o));
答:
如果没有任何内置的,您可以尝试:
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;
}
评论
isActiveClone
const a = {}; a['selfref'] = a; a['text'] = 'something'; alert(a.selfref.text);
a
isActiveClone
clone()
selfref
Arrays
Cyclic References
function clone(obj)
{ var clone = {};
clone.prototype = obj.prototype;
for (property in obj) clone[property] = obj[property];
return clone;
}
原生深度克隆
现在有一种叫做“结构化克隆”的 JS 标准,它在 Node 11 及更高版本中实验性地工作,将登陆浏览器,并且为现有系统提供 polyfill。
structuredClone(value)
如果需要,请先加载 polyfill:
import structuredClone from '@ungap/structured-clone';
有关更多详细信息,请参阅此答案。
较早的答案
数据丢失的快速克隆 - JSON.parse/stringify
如果不在对象中使用 s、functions、、、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、类型化数组或其他复杂类型,则深度克隆对象的非常简单的一行代码是:Date
undefined
Infinity
JSON.parse(JSON.stringify(object))
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
re: /.*/, // lost
}
console.log(a);
console.log(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date); // result of .toISOString()
有关基准,请参阅 Corban 的回答。
使用文库进行可靠克隆
由于克隆对象并非易事(复杂类型、循环引用、函数等),因此大多数主要库都提供克隆对象的功能。不要重新发明轮子 - 如果您已经在使用库,请检查它是否具有对象克隆功能。例如
- lodash -
克隆深度
;可以通过 lodash.clonedeep 模块单独导入,如果您还没有使用提供深度克隆功能的库,这可能是您的最佳选择 - AngularJS -
angular.copy
- jQuery -
jQuery.extend(true, { }, oldObject)
; 仅克隆 DOM 元素.clone()
- 只是库 -
只是克隆
;零依赖 npm 模块库的一部分,这些模块只做一件事。 适合各种场合的无负罪感实用程序。
评论
var A = { b: [ { a: [ 1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] } ] }; B = Object.assign( {}, A ); delete B.b[0].b;
A.b
B.b
A
typeof [] == 'object' && [] instanceof Array
法典:
// 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;
}
测试:
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;
}
评论
Object.assign({}, a)
let o = {}; o.o = o; cloneObject(o);
Date
{ a: ["foo", "bar"} }
{ a { "0": "foo", "1": "bar" } }
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});
// 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;
};
Crockford建议(我更喜欢)使用这个函数:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var newObject = object(oldObject);
它简洁明了,按预期工作,您不需要库。
编辑:
这是 的 polyfill,因此您也可以使用它。Object.create
var newObject = Object.create(oldObject);
注意:如果您使用其中的一些,您可能会遇到一些使用 .因为,创建新的空对象谁继承了.但它对于克隆对象仍然有用和实用。hasOwnProperty
create
oldObject
例如,如果oldObject.a = 5;
newObject.a; // is 5
但:
oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
对于类似数组的对象,似乎还没有理想的深度克隆运算符。如下面的代码所示,John Resig 的 jQuery 克隆器将具有非数字属性的数组转换为非数组的对象,而 RegDwight 的 JSON 克隆器会删除非数字属性。以下测试在多个浏览器上说明了这些要点:
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)
假设对象中只有属性,而没有任何函数,则可以使用:
var newObject = JSON.parse(JSON.stringify(oldObject));
评论
查看此基准: http://jsben.ch/#/bWfk9
在我之前的测试中,我发现速度是主要关注点
JSON.parse(JSON.stringify(obj))
成为深度克隆对象的最慢方式(它比 jQuery.extend 慢,标志设置为 true 10-20%)。deep
当标志设置为(浅克隆)时,jQuery.extend 非常快。这是一个不错的选择,因为它包含一些用于类型验证的额外逻辑,并且不会复制未定义的属性等,但这也会稍微减慢您的速度。deep
false
如果您知道要克隆的对象的结构,或者可以避免深度嵌套数组,则可以编写一个简单的循环来克隆对象,同时检查 hasOwnProperty,这将比 jQuery 快得多。for (var i in obj)
最后,如果您尝试在热循环中克隆已知对象结构,则只需内联克隆过程并手动构造对象即可获得更高的性能。
JavaScript 跟踪引擎在优化循环方面很糟糕,检查 hasOwnProperty 也会减慢您的速度。当速度是绝对必须的时,手动克隆。for..in
var clonedObject = {
knownProp: obj.knownProp,
..
}
请注意在对象上使用方法 - 以 ISO 格式返回日期的字符串表示形式,该格式不会转换回对象。有关更多详细信息,请参阅此答案。JSON.parse(JSON.stringify(obj))
Date
JSON.stringify(new Date())
JSON.parse()
Date
此外,请注意,至少在 Chrome 65 中,原生克隆不是要走的路。根据 JSPerf 的说法,通过创建新函数来执行本机克隆比使用 JSON.stringify 慢近 800 倍,后者在全面运行速度非常快。
如果您使用的是 Javascript ES6,请尝试使用这种本机方法进行克隆或浅拷贝。
Object.assign({}, obj);
评论
_.clone
Object.assign
JSON.parse(JSON.stringify())
let obj2 = {...obj}
Object.assign()
我认为,如果您想推广对象克隆算法,这是最好的解决方案。
它可以与jQuery一起使用,也可以不与jQuery一起使用,尽管如果您希望克隆的对象与原始对象具有相同的“类”,我建议将jQuery的扩展方法排除在外。
function clone(obj){
if(typeof(obj) == 'function')//it's a simple function
return obj;
//of it's not an object (but could be an array...even if in javascript arrays are objects)
if(typeof(obj) != 'object' || obj.constructor.toString().indexOf('Array')!=-1)
if(JSON != undefined)//if we have the JSON obj
try{
return JSON.parse(JSON.stringify(obj));
}catch(err){
return JSON.parse('"'+JSON.stringify(obj)+'"');
}
else
try{
return eval(uneval(obj));
}catch(err){
return eval('"'+uneval(obj)+'"');
}
// I used to rely on jQuery for this, but the "extend" function returns
//an object similar to the one cloned,
//but that was not an instance (instanceof) of the cloned class
/*
if(jQuery != undefined)//if we use the jQuery plugin
return jQuery.extend(true,{},obj);
else//we recursivley clone the object
*/
return (function _clone(obj){
if(obj == null || typeof(obj) != 'object')
return obj;
function temp () {};
temp.prototype = obj;
var F = new temp;
for(var key in obj)
F[key] = clone(obj[key]);
return F;
})(obj);
}
这通常不是最有效的解决方案,但它可以满足我的需求。下面的简单测试用例...
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;
}
循环阵列测试仪
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
功能测试...
f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
这是我创建的不使用原型的最快方法,因此它将在新对象中维护 hasOwnProperty。
解决方案是迭代原始对象的顶级属性,创建两个副本,从原始对象中删除每个属性,然后重置原始对象并返回新副本。它只需要迭代与顶级属性一样多的次数。这将保存所有条件以检查每个属性是否为函数、对象、字符串等,而不必迭代每个后代属性。if
唯一的缺点是必须为原始对象提供其原始创建的命名空间,以便重置它。
copyDeleteAndReset:function(namespace,strObjName){
var obj = namespace[strObjName],
objNew = {},objOrig = {};
for(i in obj){
if(obj.hasOwnProperty(i)){
objNew[i] = objOrig[i] = obj[i];
delete obj[i];
}
}
namespace[strObjName] = objOrig;
return objNew;
}
var namespace = {};
namespace.objOrig = {
'0':{
innerObj:{a:0,b:1,c:2}
}
}
var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';
console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
评论
i
我知道这是一篇旧帖子,但我认为这可能对下一个跌跌撞撞的人有所帮助。
只要您不将对象分配给任何内容,它就不会在内存中保留任何引用。因此,要创建要在其他对象之间共享的对象,必须创建如下所示的工厂:
var a = function(){
return {
father:'zacharias'
};
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
评论
如果您正在使用它,则 Underscore.js 库具有克隆方法。
var newObject = _.clone(oldObject);
评论
结构化克隆
2022 年更新:structuredClone
全局函数已经在 Firefox 94、Node 17 和 Deno 1.14 中可用
HTML 标准包括一个内部结构化克隆/序列化算法,该算法可以创建对象的深度克隆。它仍然局限于某些内置类型,但除了 JSON 支持的少数类型外,它还支持 Dates、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、类型化数组,以及将来可能更多。它还保留了克隆数据中的引用,使其能够支持会导致 JSON 错误的循环和递归结构。
Node.js 中的支持:
structuredClone
全局函数由 Node 17.0 提供:
const clone = structuredClone(original);
以前的版本:Node.js 中的模块(从 Node 11 开始)直接公开结构化序列化 API,但此功能仍标记为“实验性”,并且可能会在将来的版本中更改或删除。如果您使用的是兼容版本,则克隆对象非常简单:v8
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
浏览器中的直接支持:在 Firefox 94 中可用
structuredClone
全局函数将很快由所有主流浏览器提供(之前在 GitHub 上的 whatwg/html#793 中讨论过)。它看起来/将看起来像这样:
const clone = structuredClone(original);
在发布之前,浏览器的结构化克隆实现仅间接公开。
异步解决办法:可用。😕
使用现有 API 创建结构化克隆的开销较低的方法是通过 MessageChannel 的一个端口发布数据。另一个端口将发出一个事件,其中包含附加的 .遗憾的是,侦听这些事件必然是异步的,而同步替代方案不太实用。message
.data
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();
}
cloneAsync(value) {
return new Promise(resolve => {
const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}
const structuredCloneAsync = window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
使用示例:
const main = async () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// 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.");
};
main();
同步解决方法:太糟糕了!🤢
同步创建结构化克隆没有好的选项。这里有一些不切实际的技巧。
history.pushState()
并且两者都创建其第一个参数的结构化克隆,并将该值分配给 。您可以使用它来创建任何对象的结构化克隆,如下所示:history.replaceState()
history.state
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
使用示例:
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// 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.");
};
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
main();
虽然是同步的,但这可能非常慢。它会产生与操作浏览器历史记录相关的所有开销。重复调用此方法可能会导致 Chrome 暂时无响应。
Notification
构造函数创建其关联数据的结构化克隆。它还会尝试向用户显示浏览器通知,但除非您请求通知权限,否则这将以静默方式失败。如果您拥有用于其他目的的权限,我们将立即关闭我们创建的通知。
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
使用示例:
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// 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.");
};
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.close();
return n.data;
};
main();
评论
structuredClone
Error
structuredClone
浅文单行(ECMAScript 第 5 版):
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
和浅文单行(ECMAScript 第 6 版,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
评论
这是一个全面的 clone() 方法,可以克隆任何 JavaScript 对象。它几乎可以处理所有情况:
function clone(src, deep) {
var toString = Object.prototype.toString;
if (!src && typeof src != "object") {
// Any non-object (Boolean, String, Number), null, undefined, NaN
return src;
}
// Honor native/custom clone methods
if (src.clone && toString.call(src.clone) == "[object Function]") {
return src.clone(deep);
}
// DOM elements
if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
return src.cloneNode(deep);
}
// Date
if (toString.call(src) == "[object Date]") {
return new Date(src.getTime());
}
// RegExp
if (toString.call(src) == "[object RegExp]") {
return new RegExp(src);
}
// Function
if (toString.call(src) == "[object Function]") {
//Wrap in another method to make sure == is not true;
//Note: Huge performance issue due to closures, comment this :)
return (function(){
src.apply(this, arguments);
});
}
var ret, index;
//Array
if (toString.call(src) == "[object Array]") {
//[].slice(0) would soft clone
ret = src.slice();
if (deep) {
index = ret.length;
while (index--) {
ret[index] = clone(ret[index], true);
}
}
}
//Object
else {
ret = src.constructor ? new src.constructor() : {};
for (var prop in src) {
ret[prop] = deep
? clone(src[prop], true)
: src[prop];
}
}
return ret;
};
评论
有一个库(称为“克隆”),可以很好地做到这一点。它提供了我所知道的任意对象的最完整的递归克隆/复制。它还支持循环引用,这在其他答案中尚未涵盖。
你也可以在 npm 上找到它。它可以用于浏览器以及 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' } }
(免责声明:我是图书馆的作者。
这是上面 ConroyP 答案的一个版本,即使构造函数具有必需的参数,它也有效:
//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;
}
这个函数在我的simpleoo库中也可用。
编辑:
这是一个更强大的版本(感谢 Justin McCandless,它现在也支持循环引用):
/**
* 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();
};
}
我有两个很好的答案,这取决于你的目标是是否是克隆一个“普通的旧 JavaScript 对象”。
我们还假设您的意图是创建一个完整的克隆,其中没有对源对象的原型引用。如果你对一个完整的克隆不感兴趣,那么你可以使用其他一些答案(Crockford 模式)中提供的许多 Object.clone() 例程。
对于普通的旧 JavaScript 对象,在现代运行时中克隆对象的一种久经考验的好方法非常简单:
var clone = JSON.parse(JSON.stringify(obj));
请注意,源对象必须是纯 JSON 对象。也就是说,它的所有嵌套属性都必须是标量(如布尔值、字符串、数组、对象等)。不会克隆任何函数或特殊对象,如 RegExp 或 Date。
它有效吗?哎呀,是的。我们尝试了各种克隆方法,效果最好。我敢肯定,有些忍者会想出一种更快的方法。但我怀疑我们谈论的是边际收益。
这种方法简单易行。把它包装成一个方便的功能,如果你真的需要挤出一些收益,那就以后再去。
现在,对于非纯 JavaScript 对象,没有一个非常简单的答案。事实上,由于 JavaScript 函数和内部对象状态的动态性质,不可能有。深度克隆包含函数的 JSON 结构需要重新创建这些函数及其内部上下文。而 JavaScript 根本没有标准化的方式来做到这一点。
同样,执行此操作的正确方法是通过一种方便的方法,您可以在代码中声明和重用该方法。这种便捷方法可以赋予您对自己的对象的一些了解,因此您可以确保在新对象中正确地重新创建图形。
我们是自己写的,但我见过的最好的通用方法在这里介绍:
http://davidwalsh.name/javascript-clone
这是正确的想法。作者(David Walsh)注释了广义函数的克隆。这是您可能会选择执行的操作,具体取决于您的用例。
主要思想是,您需要在每个类型的基础上特殊处理函数(或原型类,可以这么说)的实例化。在这里,他提供了一些 RegExp 和 Date 的示例。
这段代码不仅简短,而且可读性也很强。扩展非常容易。
这有效率吗?哎呀,是的。鉴于目标是生成真正的深拷贝克隆,那么您将不得不遍历源对象图的成员。使用此方法,可以精确调整要处理的子成员以及如何手动处理自定义类型。
所以你去吧。两种方法。在我看来,两者都是有效的。
Lodash 有一个很好的 _.cloneDeep(value) 方法:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
有很多答案,但没有一个给出我需要的预期效果。我想利用jQuery的深度复制的强大功能...但是,当它遇到数组时,它只是复制对数组的引用并深度复制其中的项。为了解决这个问题,我做了一个很好的小递归函数,它会自动创建一个新数组。
(如果您愿意,它甚至可以检查 kendo.data.ObservableArray!但是,如果您希望数组再次可观察,请确保调用 kendo.observable(newItem)。
因此,要完全复制现有项目,只需执行以下操作:
var newItem = jQuery.extend(true, {}, oldItem);
createNewArrays(newItem);
function createNewArrays(obj) {
for (var prop in obj) {
if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {
var copy = [];
$.each(obj[prop], function (i, item) {
var newChild = $.extend(true, {}, item);
createNewArrays(newChild);
copy.push(newChild);
});
obj[prop] = copy;
}
}
}
我通常使用,但是,这里有一个更合适的方法:var newObj = JSON.parse( JSON.stringify(oldObje) );
var o = {};
var oo = Object.create(o);
(o === oo); // => false
观看旧版浏览器!
评论
Object.create
这是我的对象克隆器版本。这是 jQuery 方法的独立版本,只有很少的调整和调整。看看小提琴。我使用了很多jQuery,直到我意识到我大部分时间都只使用这个函数x_x。
用法与jQuery API中描述的相同:
- 非深度克隆:
extend(object_dest, object_source);
- 深度克隆:
extend(true, object_dest, object_source);
一个额外的函数用于定义对象是否适合克隆。
/**
* This is a quasi clone of jQuery's extend() function.
* by Romain WEEGER for wJs library - www.wexample.com
* @returns {*|{}}
*/
function extend() {
// Make a copy of arguments to avoid JavaScript inspector hints.
var to_add, name, copy_is_array, clone,
// The target object who receive parameters
// form other objects.
target = arguments[0] || {},
// Index of first argument to mix to target.
i = 1,
// Mix target with all function arguments.
length = arguments.length,
// Define if we merge object recursively.
deep = false;
// Handle a deep copy situation.
if (typeof target === 'boolean') {
deep = target;
// Skip the boolean and the target.
target = arguments[ i ] || {};
// Use next object as first added.
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object' && typeof target !== 'function') {
target = {};
}
// Loop trough arguments.
for (false; i < length; i += 1) {
// Only deal with non-null/undefined values
if ((to_add = arguments[ i ]) !== null) {
// Extend the base object.
for (name in to_add) {
// We do not wrap for loop into hasOwnProperty,
// to access to all values of object.
// Prevent never-ending loop.
if (target === to_add[name]) {
continue;
}
// Recurse if we're merging plain objects or arrays.
if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {
if (copy_is_array) {
copy_is_array = false;
clone = target[name] && Array.isArray(target[name]) ? target[name] : [];
}
else {
clone = target[name] && is_plain_object(target[name]) ? target[name] : {};
}
// Never move original objects, clone them.
target[name] = extend(deep, clone, to_add[name]);
}
// Don't bring in undefined values.
else if (to_add[name] !== undefined) {
target[name] = to_add[name];
}
}
}
}
return target;
}
/**
* Check to see if an object is a plain object
* (created using "{}" or "new Object").
* Forked from jQuery.
* @param obj
* @returns {boolean}
*/
function is_plain_object(obj) {
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
// - window
if (obj === null || typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
return false;
}
// Support: Firefox <20
// The try/catch suppresses exceptions thrown when attempting to access
// the "constructor" property of certain host objects, i.e. |window.location|
// https://bugzilla.mozilla.org/show_bug.cgi?id=814622
try {
if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
}
catch (e) {
return false;
}
// If the function hasn't returned already, we're confident that
// |obj| is a plain object, created by {} or constructed with new Object
return true;
}
评论
|| typeof target[name] !== "undefined"
if (target === to_add[name]) { continue; }
target
var a={hello:"world", foo:"bar"}; var b={hello:"you"}; extend(b, a);
b => {hello:"you", foo:"bar"}
b => {hello:"world", foo:"bar"}
为了将来参考,ECMAScript 6 的当前草案引入了 Object.assign 作为克隆对象的一种方式。示例代码为:
var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.log(obj2); // { a: true, b: 1 }
在撰写本文时,浏览器中的 Firefox 34 支持仅限于 Firefox 34,因此它还不能在生产代码中使用(当然,除非您正在编写 Firefox 扩展)。
评论
obj2 = Object.assign({}, obj1)
obj2 = obj1
const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456;
o1.a.deep === 456
Object.assign()
不用于克隆嵌套对象。
Warning for Deep Clone - For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.
按性能分类的深度拷贝:
根据基准
https://www.measurethat.net/Benchmarks/Show/17502/0/deep-copy-comparison 从最好到最差排名
- spread 运算符(基元数组 - 仅限)
...
slice()
(基元数组 - 仅限)splice(0)
(基元数组 - 仅限)concat()
(基元数组 - 仅限)JSON.parse(JSON.stringify())
(原始数组和文字数组 - 仅限)- 自定义函数,如下所示(任何数组)
- Lodash's(任意数组)
_.cloneDeep()
- jQuery(任意数组)
$.extend()
- 下划线(原始数组和文字数组 - 仅限)
_.clone()
哪里:
- 基元 = 字符串、数字和布尔值
- literals = 对象字面量 , 数组字面量
{}
[]
- any = 基元、文本和原型
深度复制基元数组:
let arr1a = [1, 'a', true];
要仅使用基元(即数字、字符串和布尔值)深度复制数组,可以使用重新赋值、 、 和下划线。slice()
concat()
clone()
点差表现最快的地方:
let arr1b = [...arr1a];
哪里有比和更好的性能slice()
splice(0)
concat()
let arr1d = arr1a.slice();
let arr1c = arr1a.splice(0);
let arr1e = arr1a.concat();
深层复制基元和对象文字数组:
let arr2a = [1, 'a', true, {}, []];
let arr2b = JSON.parse(JSON.stringify(arr2a));
深层复制基元、对象文本和原型的数组:
let arr3a = [1, 'a', true, {}, [], new Object()];
编写自定义函数(性能比jQuery快):$.extend()
function copy(aObject) {
// Prevent undefined objects
// if (!aObject) return aObject;
let bObject = Array.isArray(aObject) ? [] : {};
let value;
for (const key in aObject) {
// Prevent self-references to parent object
// if (Object.is(aObject[key], aObject)) continue;
value = aObject[key];
bObject[key] = (typeof value === "object") ? copy(value) : value;
}
return bObject;
}
let arr3b = copy(arr3a);
或者使用第三方实用程序函数:
let arr3c = $.extend(true, [], arr3a); // jQuery
let arr3d = _.cloneDeep(arr3a); // Lodash
评论
hasOwnProperty
Object.keys
hasOwnProperty
slice()
splice(0)
用于获取 的 和 支持,并使用循环获取可枚举的键:Object.create()
prototype
instanceof
for()
function cloneObject(source) {
var key,value;
var clone = Object.create(source);
for (key in source) {
if (source.hasOwnProperty(key) === true) {
value = source[key];
if (value!==null && typeof value==="object") {
clone[key] = cloneObject(value);
} else {
clone[key] = value;
}
}
}
return clone;
}
评论
Object.create(Object.getPrototypeOf(source))
getPrototypeOf
Array
Object
Object.create()
需要新的浏览器,但是......
让我们扩展原生对象并得到一个真正的 .extend()
;
Object.defineProperty(Object.prototype, 'extend', {
enumerable: false,
value: function(){
var that = this;
Array.prototype.slice.call(arguments).map(function(source){
var props = Object.getOwnPropertyNames(source),
i = 0, l = props.length,
prop;
for(; i < l; ++i){
prop = props[i];
if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
that[prop] = that[prop].extend(source[prop]);
}else{
Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
}
}
});
return this;
}
});
只需在对象上使用 .extend() 的任何代码之前弹出它即可。
例:
var obj1 = {
node1: '1',
node2: '2',
node3: 3
};
var obj2 = {
node1: '4',
node2: 5,
node3: '6'
};
var obj3 = ({}).extend(obj1, obj2);
console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
评论
下面创建同一对象的两个实例。我找到了它,目前正在使用它。它简单易用。
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
仅当可以使用 ECMAScript 6 或转译器时。
特征:
- 复制时不会触发 getter/setter。
- 保留 getter/setter。
- 保留原型信息。
- 适用于对象文字和函数式 OO 编写风格。
法典:
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;
}
评论
Date
null
Object.create(null) instanceof Object
对于想要使用版本但又不丢失 Date 对象的人,可以使用 parse
方法的第二个参数将字符串转换回 Date:JSON.parse(JSON.stringify(obj))
function clone(obj) {
var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
return JSON.parse(JSON.stringify(obj), function(k, v) {
if (typeof v === 'string' && regExp.test(v))
return new Date(v)
return v;
})
}
// usage:
var original = {
a: [1, null, undefined, 0, {a:null}, new Date()],
b: {
c(){ return 0 }
}
}
var cloned = clone(original)
console.log(cloned)
评论
在一行代码中克隆(而不是深度克隆)对象的有效方法
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;
}
});
}
评论
由于递归对于 JavaScript 来说太昂贵了,而且我发现的大多数答案都使用递归,而 JSON 方法将跳过不可转换的 JSON 部分(函数等)。所以我做了一些研究,发现这种蹦床技术可以避免它。代码如下:
/*
* Trampoline to avoid recursion in JavaScript, see:
* https://www.integralist.co.uk/posts/functional-recursive-javascript-programming/
*/
function trampoline() {
var func = arguments[0];
var args = [];
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
var currentBatch = func.apply(this, args);
var nextBatch = [];
while (currentBatch && currentBatch.length > 0) {
currentBatch.forEach(function(eachFunc) {
var ret = eachFunc();
if (ret && ret.length > 0) {
nextBatch = nextBatch.concat(ret);
}
});
currentBatch = nextBatch;
nextBatch = [];
}
};
/*
* Deep clone an object using the trampoline technique.
*
* @param target {Object} Object to clone
* @return {Object} Cloned object.
*/
function clone(target) {
if (typeof target !== 'object') {
return target;
}
if (target == null || Object.keys(target).length == 0) {
return target;
}
function _clone(b, a) {
var nextBatch = [];
for (var key in b) {
if (typeof b[key] === 'object' && b[key] !== null) {
if (b[key] instanceof Array) {
a[key] = [];
}
else {
a[key] = {};
}
nextBatch.push(_clone.bind(null, b[key], a[key]));
}
else {
a[key] = b[key];
}
}
return nextBatch;
};
var ret = target instanceof Array ? [] : {};
(trampoline.bind(null, _clone))(target, ret);
return ret;
};
评论
使用当今的 JavaScript 克隆对象:ECMAScript 2015(以前称为 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};
旧浏览器可能不支持 ECMAScript 2015。一个常见的解决方案是使用 JavaScript 到 JavaScript 的编译器(如 Babel)来输出 JavaScript 代码的 ECMAScript 5 版本。
正如@jim-hall所指出的,这只是一个肤浅的副本。属性的属性被复制为参考:更改一个属性将更改另一个对象/实例中的值。
评论
只是因为我没有看到提到 AngularJS,并认为人们可能想知道......
angular.copy
还提供了一种深度复制对象和数组的方法。
评论
angular.extend({},obj);
jQuery.extend
angular.extend
angular.copy
单行 ECMAScript 6 解决方案(未处理 Date/Regex 等特殊对象类型):
const clone = (o) =>
typeof o === 'object' && o !== null ? // only clone objects
(Array.isArray(o) ? // if cloning an array
o.map(e => clone(e)) : // clone each of its elements
Object.keys(o).reduce( // otherwise reduce every key in the object
(r, k) => (r[k] = clone(o[k]), r), {} // and save its cloned value into a new object
)
) :
o; // return non-objects as is
var x = {
nested: {
name: 'test'
}
};
var y = clone(x);
console.log(x.nested !== y.nested);
评论
我使用 npm 克隆库。显然,它也可以在浏览器中使用。
https://www.npmjs.com/package/clone
let a = clone(b)
AngularJS的
好吧,如果你使用的是angular,你也可以这样做
var newObject = angular.copy(oldObject);
这是一个递归的解决方案:
obj = {
a: { b: { c: { d: ['1', '2'] } } },
e: 'Saeid'
}
const Clone = function (obj) {
const container = Array.isArray(obj) ? [] : {}
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if(typeof obj[key] == 'object') {
container[key] = Clone(obj[key])
}
else
container[key] = obj[key].slice()
}
return container
}
console.log(Clone(obj))
评论
[1,2]
为了将来参考,可以使用此代码
ES6 (美):
_clone: function(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
ES5 (美) :
function clone(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
例如
var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}
xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
评论
class Handler {
static deepCopy (obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
const result = [];
for (let i = 0, len = obj.length; i < len; i++) {
result[i] = Handler.deepCopy(obj[i]);
}
return result;
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
const result = {};
for (let prop in obj) {
result[prop] = Handler.deepCopy(obj[prop]);
}
return result;
}
return obj;
}
}
克隆对象在 JS 中一直是一个问题,但在 ES6 之前,我在下面列出了在 JavaScript 中复制对象的不同方法,假设您有下面的对象并希望拥有它的深度副本:
var obj = {a:1, b:2, c:3, d:4};
有几种方法可以在不更改原点的情况下复制此对象:
ES5+,使用一个简单的函数为您完成复制:
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] = deepCopyObj(obj[i]); } return copy; } if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = deepCopyObj(obj[attr]); } return copy; } throw new Error("Unable to copy obj this object."); }
ES5+,使用 和 .
JSON.parse
JSON.stringify
var deepCopyObj = JSON.parse(JSON.stringify(obj));
角:
var deepCopyObj = angular.copy(obj);
j查询:
var deepCopyObj = jQuery.extend(true, {}, obj);
下划线.js & Lodash:
var deepCopyObj = _.cloneDeep(obj); //latest version of Underscore.js makes shallow copy
希望这些对您有所帮助...
在不触及原型继承的情况下,您可以按如下方式深化孤对象和数组;
function objectClone(o){
var ot = Array.isArray(o);
return o !== null && typeof o === "object" ? Object.keys(o)
.reduce((r,k) => o[k] !== null && typeof o[k] === "object" ? (r[k] = objectClone(o[k]),r)
: (r[k] = o[k],r), ot ? [] : {})
: o;
}
var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},
arr = [1,2,[3,4,[5,6,[7]]]],
nil = null,
clobj = objectClone(obj),
clarr = objectClone(arr),
clnil = objectClone(nil);
console.log(clobj, obj === clobj);
console.log(clarr, arr === clarr);
console.log(clnil, nil === clnil);
clarr[2][2][2] = "seven";
console.log(arr, clarr);
Lodash 有一个功能可以为您处理这个问题。
var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};
var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}
在此处阅读文档。
评论
//Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo)
我不同意这里得票最多的答案。递归深度克隆比提到的 JSON.parse(JSON.stringify(obj)) 方法快得多。
- Jsperf 在这里排名第一:https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
- 上面答案中的 Jsben 更新了递归深度克隆,以显示递归深度克隆击败了提到的所有其他克隆:http://jsben.ch/13YKQ
以下是快速参考的功能:
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
}
评论
if(o instanceof Date) return new Date(o.valueOf());
由于这个问题在参考内置功能(例如 Object.assign 或自定义代码)时得到了很多关注和答案,因此我想分享一些深度克隆库,
1. 克隆
npm install --savedev esclone https://www.npmjs.com/package/esclone
ES6 中的使用示例:
import esclone from "esclone";
const rockysGrandFather = {
name: "Rockys grand father",
father: "Don't know :("
};
const rockysFather = {
name: "Rockys Father",
father: rockysGrandFather
};
const rocky = {
name: "Rocky",
father: rockysFather
};
const rockyClone = esclone(rocky);
ES5 中的使用示例:
var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)
2. 深拷贝
npm install 深层复制 https://www.npmjs.com/package/deep-copy
例:
var dcopy = require('deep-copy')
// deep copy object
var copy = dcopy({a: {b: [{c: 5}]}})
// deep copy array
var copy = dcopy([1, 2, {a: {b: 5}}])
3. 克隆深度
$ npm install --save clone-deep https://www.npmjs.com/package/clone-deep
例:
var cloneDeep = require('clone-deep');
var obj = {a: 'b'};
var arr = [obj];
var copy = cloneDeep(arr);
obj.c = 'd';
console.log(copy);
//=> [{a: 'b'}]
console.log(arr);
有很多方法可以实现这一点,但如果你想在没有任何库的情况下做到这一点,你可以使用以下方法:
const cloneObject = (oldObject) => {
let newObject = oldObject;
if (oldObject && typeof oldObject === 'object') {
if(Array.isArray(oldObject)) {
newObject = [];
} else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
newObject = new Date(oldObject.getTime());
} else {
newObject = {};
for (let i in oldObject) {
newObject[i] = cloneObject(oldObject[i]);
}
}
}
return newObject;
}
让我知道你的想法。
我回答这个问题已经晚了,但我有另一种克隆对象的方法:
function cloneObject(obj) {
if (obj === null || typeof(obj) !== 'object')
return obj;
var temp = obj.constructor(); // changed
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = cloneObject(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
var b = cloneObject({"a":1,"b":2}); // calling
然后更好更快:
var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));
和
var a = {"a":1,"b":2};
// Deep copy
var newObject = jQuery.extend(true, {}, a);
我已经对代码进行了基准标记,您可以在此处测试结果:
并分享结果: 参考资料: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
评论
obj['isActiveClone'] = null
obj.hasOwnProperty(key)
这是我使用默认值和扩展运算符深度克隆对象的方法ES2015
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
const testObj = {
"type": "object",
"properties": {
"userId": {
"type": "string",
"chance": "guid"
},
"emailAddr": {
"type": "string",
"chance": {
"email": {
"domain": "fake.com"
}
},
"pattern": "[email protected]"
}
},
"required": [
"userId",
"emailAddr"
]
}
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
console.log(makeDeepCopy(testObj))
评论
test
undefined
null
异步对象克隆呢?Promise
async function clone(thingy /**/)
{
if(thingy instanceof Promise)
{
throw Error("This function cannot clone Promises.");
}
return thingy;
}
评论
Promise.resolve(value)
value
const newObj = obj
纵观这一长串答案,除了我知道的答案外,几乎所有的解决方案都已涵盖。这是深度克隆对象的 VANILLA JS 方法列表。
JSON.parse(JSON.stringify( obj ) );
通过带有 pushState 或 replaceState 的 history.state
Web 通知 API,但这有一个缺点,即要求用户提供权限。
在对象中执行自己的递归循环以复制每个级别。
我没有看到的答案 -> 使用 ServiceWorkers。在页面和 ServiceWorker 脚本之间来回传递的消息(对象)将是任何对象的深度克隆。
评论
ES 2017 示例:
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
评论
{ foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3}
{ foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}
fooBaz: 44
testObj2
testObj3
)
根据我的经验,递归版本的性能远远优于 .下面是一个现代化的递归深度对象复制函数,可以放在一行上:JSON.parse(JSON.stringify(obj))
function deepCopy(obj) {
return Object.keys(obj).reduce((v, d) => Object.assign(v, {
[d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
}), {});
}
这比该方法快 40 倍左右。JSON.parse...
评论
Object
null.constructor
对于浅楷贝,ECMAScript2018 标准中引入了一种简单易用的方法。它涉及使用点差运算符:
let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };
我已经在 Chrome 浏览器中对其进行了测试,这两个对象都存储在不同的位置,因此更改任何一个对象中的直接子值都不会更改另一个。虽然(在示例中)更改值将影响两个副本。e
这种技术非常简单明了。我认为这是一劳永逸地解决这个问题的真正最佳实践。
评论
obj.e
objClone.e
希望这会有所帮助。
function deepClone(obj) {
/*
* Duplicates an object
*/
var ret = null;
if (obj !== Object(obj)) { // primitive types
return obj;
}
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
ret = obj; // for ex: obj = new String("Spidergap")
} else if (obj instanceof Date) { // date
ret = new obj.constructor();
} else
ret = Object.create(obj.constructor.prototype);
var prop = null;
var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also
var props = {};
for (var i in allProps) {
prop = allProps[i];
props[prop] = false;
}
for (i in obj) {
props[i] = i;
}
//now props contain both enums and non enums
var propDescriptor = null;
var newPropVal = null; // value of the property in new object
for (i in props) {
prop = obj[i];
propDescriptor = Object.getOwnPropertyDescriptor(obj, i);
if (Array.isArray(prop)) { //not backward compatible
prop = prop.slice(); // to copy the array
} else
if (prop instanceof Date == true) {
prop = new prop.constructor();
} else
if (prop instanceof Object == true) {
if (prop instanceof Function == true) { // function
if (!Function.prototype.clone) {
Function.prototype.clone = function() {
var that = this;
var temp = function tmp() {
return that.apply(this, arguments);
};
for (var ky in this) {
temp[ky] = this[ky];
}
return temp;
}
}
prop = prop.clone();
} else // normal object
{
prop = deepClone(prop);
}
}
newPropVal = {
value: prop
};
if (propDescriptor) {
/*
* If property descriptors are there, they must be copied
*/
newPropVal.enumerable = propDescriptor.enumerable;
newPropVal.writable = propDescriptor.writable;
}
if (!ret.hasOwnProperty(i)) // when String or other predefined objects
Object.defineProperty(ret, i, newPropVal); // non enumerable
}
return ret;
}
https://github.com/jinujd/Javascript-Deep-Clone
在 JavaScript 中深度复制对象(我认为最好和最简单)
1. 使用 JSON.parse(JSON.stringify(object));
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }
2.使用创建方法
function cloneObject(obj) {
var clone = {};
for(var i in obj) {
if(obj[i] != null && typeof(obj[i])=="object")
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = cloneObject(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }
3.使用 Lo-Dash 的 _.cloneDeep 链接 lodash
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }
4.使用 Object.assign() 方法
var obj = {
a: 1,
b: 2
}
var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }
但是错了
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.
5.使用下划线.js _.clone链接下划线.js
var obj = {
a: 1,
b: 2
}
var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }
但是错了
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)
JSBEN的。CH 性能基准测试游乐场 1~3 http://jsben.ch/KVQLd
评论
defaultsDeep
(i === '__proto__')
(i === 'constuctor' && typeof obj[i] === 'function')
当您的对象是嵌套的并且它包含数据对象、其他结构化对象或某些属性对象等时,请使用或将不起作用。在这种情况下,请使用 lodash。它简单易行。JSON.parse(JSON.stringify(object))
Object.assign({}, obj)
$.extend(true, {}, obj)
var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
var A = _.cloneDeep(obj);
现在 A 将是您在没有任何引用的情况下新克隆的 obj。
如果您发现自己经常做这类事情(例如 - 创建撤消重做功能),可能值得研究 Immutable.js
const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );
const map2 = map1.setIn( [ 'c', 'd' ], 50 );
console.log( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); // "3 vs 50"
https://codepen.io/anon/pen/OBpqNE?editors=1111
在 JavaScript 中,你可以编写如下方法
deepCopy
function deepCopy(src) {
let target = Array.isArray(src) ? [] : {};
for (let prop in src) {
let value = src[prop];
if(value && typeof value === 'object') {
target[prop] = deepCopy(value);
} else {
target[prop] = value;
}
}
return target;
}
评论
prop
(prop === 'constuctor' && typeof src[prop] === 'function')
(prop === '__proto__')
如何将对象的键与其值合并?
function deepClone(o) {
var keys = Object.keys(o);
var values = Object.values(o);
var clone = {};
keys.forEach(function(key, i) {
clone[key] = typeof values[i] == 'object' ? Object.create(values[i]) : values[i];
});
return clone;
}
注意:此方法不一定会进行浅拷贝,但它只复制一个内部对象的深度,这意味着当您得到类似 的东西时,它只会克隆直接位于其中的对象,因此从技术上讲是对 的引用,而是克隆,而不是引用。{a: {b: {c: null}}}
deepClone(a.b).c
a.b.c
deepClone(a).b
评论
null
function clone(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
使用以下方法而不是因为
它比以下方法慢JSON.parse(JSON.stringify(obj))
随着新方法Object.fromEntries()的提出,该方法在某些浏览器的较新版本上受支持(参考)。我想为下一个递归方法做出贡献:
const obj = {
key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
key2: {key21: "key21", key22: "key22"},
key3: "key3",
key4: [1,2,3, {key: "value"}]
}
const cloneObj = (obj) =>
{
if (Object(obj) !== obj)
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
我的情况有点不同。我有一个带有嵌套对象和函数的对象。因此,不是我问题的解决方案。使用第三方库对我来说也不是一种选择。Object.assign()
JSON.stringify()
因此,我决定制作一个简单的函数,使用内置方法来复制具有文本属性、嵌套对象和函数的对象。
let deepCopy = (target, source) => {
Object.assign(target, source);
// check if there's any nested objects
Object.keys(source).forEach((prop) => {
/**
* assign function copies functions and
* literals (int, strings, etc...)
* except for objects and arrays, so:
*/
if (typeof(source[prop]) === 'object') {
// check if the item is, in fact, an array
if (Array.isArray(source[prop])) {
// clear the copied referenece of nested array
target[prop] = Array();
// iterate array's item and copy over
source[prop].forEach((item, index) => {
// array's items could be objects too!
if (typeof(item) === 'object') {
// clear the copied referenece of nested objects
target[prop][index] = Object();
// and re do the process for nested objects
deepCopy(target[prop][index], item);
} else {
target[prop].push(item);
}
});
// otherwise, treat it as an object
} else {
// clear the copied referenece of nested objects
target[prop] = Object();
// and re do the process for nested objects
deepCopy(target[prop], source[prop]);
}
}
});
};
下面是一个测试代码:
let a = {
name: 'Human',
func: () => {
console.log('Hi!');
},
prop: {
age: 21,
info: {
hasShirt: true,
hasHat: false
}
},
mark: [89, 92, { exam: [1, 2, 3] }]
};
let b = Object();
deepCopy(b, a);
a.name = 'Alien';
a.func = () => { console.log('Wassup!'); };
a.prop.age = 1024;
a.prop.info.hasShirt = false;
a.mark[0] = 87;
a.mark[1] = 91;
a.mark[2].exam = [4, 5, 6];
console.log(a); // updated props
console.log(b);
对于与效率相关的问题,我相信这是解决我遇到的问题的最简单、最有效的解决方案。我将不胜感激对该算法的任何评论,以使其更有效率。
评论
null
{}
typeof null === "object"
Object.assign({},sourceObj)
仅当对象的属性没有引用类型键时,才克隆对象。
前任
obj={a:"lol",b:["yes","no","maybe"]}
clonedObj = Object.assign({},obj);
clonedObj.b.push("skip")// changes will reflected to the actual obj as well because of its reference type.
obj.b //will also console => yes,no,maybe,skip
因此,对于深度克隆是不可能以这种方式实现的。
最好的解决方案是
var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);
评论
Object.assign()
Json
JSON
这是我的解决方案,无需使用任何库或本机 javascript 函数。
function deepClone(obj) {
if (typeof obj !== "object") {
return obj;
} else {
let newObj =
typeof obj === "object" && obj.length !== undefined ? [] : {};
for (let key in obj) {
if (key) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
}
评论
const o = {}; o.a = o; deepClone(o);
length
{ foo: "bar", length: "10cm" }
{ foo: null }
{ foo: {} }
评论
eval()
这通常是一个坏主意,因为许多 Javascript 引擎的优化器在处理通过eval
设置的变量时必须关闭。仅仅在代码中加入可能会导致性能下降。eval()
JSON
JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))
{a: null, b: null, c: null, g: false}