我知道已经有很多答案和评论争论它们不起作用。唯一的共识是“它太复杂了,没有人为此制定标准”。然而,在stackoverflow上大多数被接受的答案都揭示了被广泛使用的“简单技巧”。因此,对于像我这样的非专家,想通过更好地掌握JavaScript的复杂性来编写更安全的代码的人,我将尝试阐明一些问题。
在开始之前,让我澄清两点:
[免责声明] 我下面提出了一个函数,它解决了如何深入循环遍历
javascript对象进行复制,并说明了通常太简短的注释。它不是生产就绪的。为了清晰起见,我故意忽略了其他考虑因素,比如
循环对象(通过集合或无冲突符号属性跟踪),复制引用值或
深度克隆,不可变目标对象(再次深度克隆?),
每种类型的对象的逐案例研究,通过
访问器获取/设置属性... 此外,我没有测试性能-尽管这很重要-因为这也不是重点。
我将使用
复制或
分配术语代替
合并。因为在我看来,
合并是保守的,应该在冲突时失败。在这里,当发生冲突时,我们希望源覆盖目标。像
Object.assign
一样。
使用for..in
或Object.keys
的答案是误导性的
制作深拷贝似乎是基本和常见的做法,我们期望能够找到一个一行代码或至少通过简单递归快速解决的方法。我们不认为我们需要一个库或编写一个100行的自定义函数。
当我第一次阅读
Salakar's answer时,我真诚地认为我可以做得更好、更简单(您可以将其与
x={a:1}, y={a:{b:1}}
上的
Object.assign
进行比较)。然后我阅读了
the8472's answer,我想……这样很难逃脱,改进已经给出的答案不会让我们走得更远。
让我们暂时把深拷贝和递归放在一边。只考虑人们如何(错误地)解析属性以复制一个非常简单的对象。
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 }
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 }
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 }
Object.keys
会省略自身的不可枚举属性、自身的符号键属性和所有原型的属性。如果你的对象没有这些属性,那么这可能是可以的。但请注意,Object.assign
处理自身的符号键枚举属性。因此,你的自定义复制将失去其效果。
for..in
会提供源的属性、其原型的属性以及整个原型链的属性,而你并不想要(或知道)它们。你的目标可能会有太多的属性,混淆原型属性和自身属性。
如果你正在编写一个通用函数,并且没有使用Object.getOwnPropertyDescriptors
、Object.getOwnPropertyNames
、Object.getOwnPropertySymbols
或Object.getPrototypeOf
,那么你很可能做错了。
编写函数前需要考虑的事情
首先,请确保你理解Javascript对象是什么。在Javascript中,一个对象由自身的属性和(父级)原型对象组成。原型对象又由自身的属性和原型对象组成。依此类推,定义了一个原型链。
属性是一个键(string
或symbol
)和描述符(value
或get
/set
访问器,以及像enumerable
这样的属性)的对。
最后,有许多类型的对象。你可能想要以不同的方式处理一个对象Object和一个对象Date或一个对象Function。
因此,在编写深拷贝时,您应该至少回答以下问题:
- 我认为什么是深度拷贝(适合递归查找)或平面拷贝?
- 我想要复制哪些属性?(可枚举/不可枚举,字符串键入/符号键入,自有属性/原型的自有属性,值/描述符...)
对于我的示例,我认为只有“对象Object”是“深层”的,因为由其他构造函数创建的其他对象可能不适合进行深入查看。定制自这个SO。
function toType(a) {
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
我创建了一个options
对象来选择要复制的内容(仅供演示目的)。
const options = {nonEnum:true, symbols:true, descriptors: true, proto:true}
提议的功能
您可以在这个plunker中测试它。
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
if (descriptor.enumerable || options.nonEnum) {
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
if (options.descriptors)
Object.defineProperty(target, property, descriptor);
else
target[property] = descriptor.value;
}
}
Object.getOwnPropertyNames(source).forEach(copyProperty);
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
if (options.proto)
deepAssign(Object.assign({},options,{proto:false})) (
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
可以像这样使用:
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y);
const merge = (p, c) => Object.keys(p).forEach(k => !!p[k] && p[k].constructor === Object ? merge(p[k], c[k]) : c[k] = p[k])
- Xaqronhttps://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6
- Nwawel A Iroume