我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
当前回答
使用(…)复制对象
//bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2,c: 3 }
//good
const originalObj = { id: 5, name: 'San Francisco'};
const copyObject = {...originalObj, pincode: 4444};
console.log(copyObject) //{ id: 5, name: 'San Francisco', pincode: 4444 }
同样可以用于将阵列从一个复制到另一个
const itemsCopy = [...items];
其他回答
不同的
仅复制顶层:{…object}和object.assign({},object)
让objA={a: “键a”,b:{c: “键c”,}}let objB=Object.assign({},objA);//或{…objB}//更改对象BobjB.a=“更改objA.a(顶部)”console.log(“objA.a(top)无更改:\n”+JSON.stringify(objA,false,2));objB.b.c=“更改应仅适用于objB.b.c,但在objA.b.c中”console.log(“objA.a.c第二级已更改:\n”+JSON.stringify(objA,false,2));
对于深度复制,请使用旧浏览器的structuredClone()2022或JSON.parse(JSON.stringify(object)),无需黑客即可轻松完成。
让objA={a: “键a”,b:{c: “键c”,}}让objB=typeof structuredClone==“函数”?structuredClone(objA):JSON.parse(JSON.stringify(objB));//更改对象BobjB.a=“更改objA.a(顶部)”objB.b.c=“更改应该只针对objB.c,但它在objA.c中”console.log(“objA没有更改:\n”+JSON.stringify(objA,false,2));
这是对A.Levy代码的修改,以处理函数和多个/循环引用的克隆。这意味着,如果克隆的树中的两个财产是同一对象的引用,则克隆的对象树将使这些财产指向被引用对象的同一克隆。这也解决了循环依赖性的问题,如果不加以处理,就会导致无限循环。算法的复杂度为O(n)
function clone(obj){
var clonedObjectsArray = [];
var originalObjectsArray = []; //used to remove the unique ids when finished
var 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 Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0; i < obj.length; ++i) {
copy[i] = cloneRecursive(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
if (clonedObjectsArray[objectId(obj)] != undefined)
return clonedObjectsArray[objectId(obj)];
var copy;
if (obj instanceof Function)//Handle Function
copy = function(){return obj.apply(this, arguments);};
else
copy = {};
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 ids
for (var i = 0; i < originalObjectsArray.length; i++)
{
delete originalObjectsArray[i].__obj_id;
};
return cloneObj;
}
一些快速测试
var auxobj = {
prop1 : "prop1 aux val",
prop2 : ["prop2 item1", "prop2 item2"]
};
var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;
obj.f1 = function (){
this.prop1 = "prop1 val changed by f1";
};
objclone = clone(obj);
//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));
objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));
objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
使用(…)复制对象
//bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2,c: 3 }
//good
const originalObj = { id: 5, name: 'San Francisco'};
const copyObject = {...originalObj, pincode: 4444};
console.log(copyObject) //{ id: 5, name: 'San Francisco', pincode: 4444 }
同样可以用于将阵列从一个复制到另一个
const itemsCopy = [...items];
(以下内容主要是@Maciej Bukowski、@A.Levy、@Jan Turoğ、@Redu的回答,以及@LeviRoberts、@RobG的评论的整合,非常感谢他们!!!)
深度复制?-对!(大部分);浅拷贝?-不!(代理除外)。
我真诚欢迎大家来测试clone()。此外,defineProp()设计用于轻松快速地(重新)定义或复制任何类型的描述符。
作用
function clone(object) {
/*
Deep copy objects by value rather than by reference,
exception: `Proxy`
*/
const seen = new WeakMap()
return clone(object)
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:
_object = copyFn(object)
break
case RegExp:
_object = new RegExp(object)
break
default:
switch (Object.prototype.toString.call(object.constructor)) {
// // Stem from:
case "[object Function]":
switch (object[Symbol.toStringTag]) {
case undefined:
_object = cloneObject(object) // `class`
break
case "AsyncFunction":
case "GeneratorFunction":
case "AsyncGeneratorFunction":
_object = copyFn(object)
break
default:
_object = object
}
break
case "[object Undefined]": // `Object.create(null)`
_object = cloneObject(object)
break
default:
_object = object // `Proxy`
}
}
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)
)
}
function convertFnToStr(fn) {
let fnStr = String(fn)
if (fn.name.startsWith("[")) // isSymbolKey
fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)
? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
return fnStr
}
function copyFn(fn) {
const newFn = new Function(`return ${convertFnToStr(fn)}`)()
copyPropDescs(newFn, fn)
return newFn
}
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
})
}
//测试
const obj0 = {
u: undefined,
nul: null,
t: true,
num: 9,
str: "",
sym: Symbol("symbol"),
[Symbol("e")]: Math.E,
arr: [[0], [1, 2]],
d: new Date(),
re: /f/g,
get g() { return 0 },
o: {
n: 0,
o: { f: function (...args) { } }
},
f: {
getAccessorStr(object) {
return []
.concat(...
Object.values(Object.getOwnPropertyDescriptors(object))
.filter(desc => desc.writable === undefined)
.map(desc => Object.values(desc))
)
.filter(prop => typeof prop === "function")
.map(String)
},
f0: function f0() { },
f1: function () { },
f2: a => a / (a + 1),
f3: () => 0,
f4(params) { return param => param + params },
f5: (a, b) => ({ c = 0 } = {}) => a + b + c
}
}
defineProp(obj0, "s", { set(v) { this._s = v } })
defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } })
obj0.arr[0].name = "nested array"
let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", { set(v) { this._s = v + 1 } })
defineProp(obj1.re, "multiline", { value: true })
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Routinely")
console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()
console.log("obj0\n ",
".arr.tint:", obj0.arr.tint, "\n ",
".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
".arr.tint:", obj1.arr.tint, "\n ",
".arr[0].name:", obj1.arr[0].name
)
console.log()
console.log("Accessor-type descriptor\n ",
"of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
"of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
"set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
" → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)
console.log("—— obj0 has not been interfered.")
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - More kinds of functions")
const fnsForTest = {
f(_) { return _ },
func: _ => _,
aFunc: async _ => _,
async function() { },
async asyncFunc() { },
aFn: async function () { },
*gen() { },
async *asyncGen() { },
aG1: async function* () { },
aG2: async function* gen() { },
*[Symbol.iterator]() { yield* Object.keys(this) }
}
console.log(Reflect.ownKeys(fnsForTest).map(k =>
`${String(k)}:
${fnsForTest[k].name}-->
${String(fnsForTest[k])}`
).join("\n"))
const normedFnsStr = `{
f: function f(_) { return _ },
func: _ => _,
aFunc: async _ => _,
function: async function() { },
asyncFunc: async function asyncFunc() { },
aFn: async function () { },
gen: function* gen() { },
asyncGen: async function* asyncGen() { },
aG1: async function* () { },
aG2: async function* gen() { },
[Symbol.iterator]: function* () { yield* Object.keys(this) }
}`
const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`))
console.log("Comparison of fnsForTest and its clone:",
Reflect.ownKeys(fnsForTest).map(k =>
[k, fnsForTest[k] === copiedFnsForTest[k]]
)
)
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Circular structures")
obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr
obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log("Clear obj0's recursion:",
obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
"obj0\n ",
".o.r:", obj0.o.r, "\n ",
".arr:", obj0.arr
)
console.log(
"obj1\n ",
".o.r:", obj1.o.r, "\n ",
".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Classes")
class Person {
constructor(name) {
this.name = name
}
}
class Boy extends Person { }
Boy.prototype.sex = "M"
const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }
const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"
boy0.name = "one"
boy1.name = "neo"
console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)
工具书类
Object.create()| MDN对象.defineProperties()| MDN财产的可枚举性和所有权| MDNTypeError:循环对象值|MDN
使用的语言技巧
有条件地将道具添加到对象
短而甜:
let clone = Object.fromEntries(Object.entries(obj));
演示:
让obj={a:'b'};let clone=Object.fromEntries(Object.entries(obj));clone.a=“c”;console.log(obj,克隆);