我知道已经有很多答案,也有很多评论认为它们行不通。唯一的共识是,它太复杂了,没有人为它制定标准。然而,SO中大多数公认的答案都暴露了被广泛使用的“简单技巧”。因此,对于像我这样不是专家,但希望通过掌握javascript的复杂性来编写更安全的代码的所有人,我将尝试提供一些启发。
在开始之前,让我先澄清两点:
[DISCLAIMER] I propose a function below that tackles how we deep loop into javascript objects for copy and illustrates what is generally too shortly commented. It is not production-ready. For sake of clarity, I have purposedly left aside other considerations like circular objects (track by a set or unconflicting symbol property), copying reference value or deep clone, immutable destination object (deep clone again?), case-by-case study of each type of objects, get/set properties via accessors... Also, I did not test performance -although it's important- because it's not the point here either.
I'll use copy or assign terms instead of merge. Because in my mind a merge is conservative and should fail upon conflicts. Here, when conflicting, we want the source to overwrite the destination. Like Object.assign does.
用for. in或Object回答。钥匙容易误导人
创建深度复制似乎是非常基本和常见的实践,我们希望通过简单的递归找到一行代码,或者至少是快速获胜。我们不期望我们需要一个库或编写一个100行的自定义函数。
当我第一次读Salakar的回答时,我真的认为我可以做得更好,更简单(你可以把它与Object进行比较。赋值x={a:1}, y={a:{b:1}})。然后我读了8472的答案,我想…没有那么容易的脱身,改进已经给出的答案不会让我们走得太远。
让我们暂时把深度复制和递归放在一边。只要考虑一下人们是如何(错误地)解析属性来复制一个非常简单的对象。
const y = Object.create(
{ proto : 1 },
{ a: { enumerable: true, value: 1},
[Symbol('b')] : { enumerable: true, value: 1} } )
Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
对象。键将忽略自己的不可枚举属性,自己的符号键属性和所有原型的属性。如果你的对象没有这些也没关系。但要记住那个对象。为句柄分配自己的符号键可枚举属性。所以你的自定义拷贝失去了它的光彩。
in将在你不需要(或不知道)的情况下提供源代码、原型和完整原型链的属性。你的目标可能会有太多的属性,混淆了原型属性和自己的属性。
如果你在写一个通用函数而你没有使用Object。getOwnPropertyDescriptors,对象。getOwnPropertyNames,对象。getOwnPropertySymbols或Object。getPrototypeOf,你很可能做错了。
在编写函数之前需要考虑的事情
首先,确保您理解Javascript对象是什么。在Javascript中,一个对象由它自己的属性和(父)原型对象组成。原型对象又由它自己的属性和原型对象组成。以此类推,定义一个原型链。
属性是一对键(字符串或符号)和描述符(值或get/set访问器,以及像enumerable这样的属性)。
最后,还有许多类型的对象。您可能希望以不同的方式处理对象(object)与对象(Date)或对象(Function)。
因此,在撰写深度文案时,你至少应该回答以下问题:
我认为什么是深(适合递归查找)或平?
我想复制哪些属性?(可枚举/不可枚举,字符串键控/符号键控,自己的属性/原型自己的属性,值/描述符…)
对于我的例子,我认为只有对象Objects是深度的,因为由其他构造函数创建的其他对象可能不适合进行深入研究。从这个SO定制。
function toType(a) {
// Get fine type (object, array, function, null, error, date ...)
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
我创建了一个选项对象来选择要复制的内容(用于演示目的)。
const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
提出了功能
你可以用这个活塞测试。
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
// Copy source's own properties into target's own properties
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
//default: omit non-enumerable properties
if (descriptor.enumerable || options.nonEnum) {
// Copy in-depth first
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
//default: omit descriptors
if (options.descriptors)
Object.defineProperty(target, property, descriptor); // shallow copy descriptor
else
target[property] = descriptor.value; // shallow copy value only
}
}
// Copy string-keyed properties
Object.getOwnPropertyNames(source).forEach(copyProperty);
//default: omit symbol-keyed properties
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
//default: omit prototype's own properties
if (options.proto)
// Copy souce prototype's own properties into target prototype's own properties
deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
可以这样用:
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }