obj = eval(uneval(o));
,但这是非标准的,只有Firefox支持。我已经尝试过像
obj = JSON.parse(JSON.stringify(o));
这样的方式,但质疑其效率。我也看到了递归复制函数的各种缺陷。
我很惊讶没有一个权威的解决方案存在。
obj = eval(uneval(o));
,但这是非标准的,只有Firefox支持。obj = JSON.parse(JSON.stringify(o));
这样的方式,但质疑其效率。现在有一个名为"结构化克隆"的JS标准,在Node 11及更高版本中进行实验,将在浏览器中使用,并且已经为现有系统提供了多种填充。
structuredClone(value)
如果需要,先加载 polyfill:
import structuredClone from '@ungap/structured-clone';
查看这个答案以获取更多详细信息。
如果您的对象中没有使用Date
、函数、undefined
、Infinity
、RegExps、Maps、Sets、Blobs、FileLists、ImageData、稀疏数组、Typed Arrays或其他复杂类型,那么一个非常简单的一行代码可以深度克隆一个对象:
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's answer获取基准测试结果。
由于对象克隆并不是一件简单的事情(涉及到复杂类型、循环引用、函数等),大多数主要的库都提供了克隆对象的函数。 不要重复造轮子 - 如果您已经在使用库,请检查它是否有对象克隆函数。例如:
cloneDeep
; 可以通过lodash.clonedeep模块单独导入,如果您尚未使用提供深度克隆功能的库,则可能是您的最佳选择angular.copy
jQuery.extend(true, { }, oldObject)
; .clone()
只能克隆DOM元素just-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! - Gabriel HautclocqA.b
或B.b
都指向内存中的同一对象。如果A
具有非对象值的属性(例如数字或字符串),则会正常复制该值。但当复制包含对象值的属性时,它是按引用而不是按值复制的。此外,请记住,在JS中,数组是一个对象。 证据:typeof [] == 'object' && [] instanceof Array
。 - Unicornist请查看这个基准测试:http://jsben.ch/#/bWfk9
在我之前的一些速度相关测试中,我发现
JSON.parse(JSON.stringify(obj))
这可能是深度克隆对象最慢的方法(比设置jQuery.extend中的deep
标志为true时要慢10-20%)。
当deep
标志设置为false
(浅拷贝)时,jQuery.extend非常快。它是一个很好的选择,因为它包括一些额外的逻辑用于类型验证,并且不会复制未定义的属性等,但这也会使速度变慢一些。
如果您知道要克隆的对象的结构或可以避免深嵌套数组,则可以编写一个简单的for (var i in obj)
循环来克隆对象并检查hasOwnProperty
,这将比使用jQuery快得多。
最后,如果您试图在热循环中克隆已知的对象结构,则可以通过内联克隆过程并手动构造对象来获得更高的性能。
for..in
循环和检查hasOwnProperty
会使JavaScript跟踪引擎的优化效果不佳,也会减慢速度。当速度是绝对必要的时候,请手动克隆。
var clonedObject = {
knownProp: obj.knownProp,
..
}
请注意,不要在Date
对象上使用JSON.parse(JSON.stringify(obj))
方法- JSON.stringify(new Date())
以ISO格式返回日期的字符串表示形式,JSON.parse()
无法将其转换回Date
对象。 有关更多详细信息,请参见此答案。
此外,请注意,在至少Chrome 65中,本机克隆不是正确的方法。根据JSPerf的数据,通过创建新函数进行本机克隆的速度几乎比使用在所有情况下都非常快的JSON.stringify慢800倍。
如果您正在使用Javascript ES6,请尝试使用此本机方法进行克隆或浅拷贝。
Object.assign({}, obj);
_.clone
和 Object.assign
)与一些深克隆(JSON.parse(JSON.stringify())
)进行了比较。其次,它在 lodash 中标记为“深克隆”,但实际上却只做了浅克隆。 - papillonlet obj2 = {...obj}
似乎比 Object.assign()
更高效。大约快20%。 - ragan2022更新: structuredClone
全局函数已经在Firefox 94、Node 17和Deno 1.14中可用。
HTML标准包括一个内部的结构化克隆/序列化算法,可以创建对象的深层副本。它仍然限于某些内置类型,但除了JSON支持的少数类型外,还支持Dates、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、Typed Arrays等,未来可能还会支持更多。它还保留了克隆数据中的引用,使其能够支持JSON会导致错误的循环和递归结构。
Node 17.0提供了structuredClone
全局函数。
const clone = structuredClone(original);
以前的版本: 在Node.js的v8
模块(截至Node 11)中直接公开了结构化序列化API,但此功能仍被标记为“实验性的”,并可能在将来的版本中更改或删除。 如果您正在使用兼容版本,则克隆对象就像这样简单:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
structuredClone
全局函数很快将由所有主流浏览器提供(此前已在GitHub上讨论过)。它看起来/将会是这样的:
const clone = structuredClone(original);
在这个被提及的功能发布之前,浏览器的结构化克隆实现只能以间接的方式使用。
现有的API中,通过一个MessageChannels发送数据到一个端口是创建结构化克隆的低开销方式,而另一个端口将会发出一个带有附加数据结构化克隆的message
事件。不幸的是,监听这些事件必须是异步的,同步的替代方案不太实用。
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
已在Firefox 94+、Chrome 98+、Safari 15.4+和Edge 98+中可用,因此在所有主流浏览器的当前版本中都可以使用! - oelna假设你的对象仅包含属性而没有任何函数,你可以直接使用:
var newObject = JSON.parse(JSON.stringify(oldObject));
如果没有内置的选项,你可以尝试:
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
部分的意思吗? - era s'qconst a = {}; a['selfref'] = a; a['text'] = 'something'; alert(a.selfref.text);
如果您尝试克隆上述 a
对象而没有 isActiveClone
部分,则会陷入无限递归(因为属性 selfref
上的递归 clone()
调用)。 - Peti29Object.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;
}
});
}
性能深拷贝:
根据基准测试,从最好到最差排序如下:
https://www.measurethat.net/Benchmarks/Show/17502/0/deep-copy-comparison
...
(仅适用于原始数组)slice()
(仅适用于原始数组)splice(0)
(仅适用于原始数组)concat()
(仅适用于原始数组)JSON.parse(JSON.stringify())
(仅适用于原始和字面量数组)_.cloneDeep()
(任何数组)$.extend()
(任何数组)_.clone()
(仅适用于原始和字面量数组)其中:
{}
,数组字面量 []
深拷贝一个原始值数组:
let arr1a = [1, 'a', true];
要深复制仅包含原始数据类型的数组(即数字、字符串和布尔值),可以使用重新赋值、slice()
、concat()
和 Underscore 的 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
来排除继承的属性。我使用(可能更快的)普通for循环遍历Object.keys
。 - mikiqexhasOwnProperty
方法会为每个键创建性能损失(将函数调用推入和弹出堆栈,并执行方法代码)。 - tim-montagueslice()
比 splice(0)
快得多,尽管它们都是浅拷贝。 - S.Serpooshan这是我正在使用的代码:
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)
却可以。 - Martin Luther ETOUMANlet o = {}; o.o = o; cloneObject(o);
- Gershom Maes{ a: ["foo", "bar"} }
将变成 { a { "0": "foo", "1": "bar" } }
。 - zkldi代码:
// 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);
在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.使用created方法
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.使用Underscore.js的_.clone方法 链接 Underscore.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')
,也不应进行复制。 - Frank Fajardo
eval()
通常是不明智的,因为许多JavaScript引擎的优化器必须在处理通过eval()
设置的变量时关闭。仅仅使用eval()
就可能导致代码性能更差。 - user56reinstatemonica8JSON
方法会丢失任何在JSON中没有等价的JavaScript类型。例如: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}
。 - oriadam