Javascript 是我最喜欢的编程语言,它有些古怪已经不是什么新闻了。 其中之一就是对象复制的方式。 看看下面的代码片段:
let myObj = { name: 'James'}let secObj = myObj;myObj.name = 'John'console.log(secObj.name) // John
在这里更改 myObj.name 的值也会导致 secObj.name 的值更改。 这是一种奇怪的行为,但是这是正常的,因为 javascript 通过引用传递对象。 这意味着在第二个代码块中,创建 myObj 时,javascript 在内存中为对象创建一个位置,而 myObj 包含对创建的对象的引用。 因此,在第4行中,当 secObj 被创建并设置为 myObj 值时,secObj 现在被传递到对象的引用。 在此过程中不会创建任何新对象。 和 secObj 都引用同一个对象。 因此,编辑 myObj.x 会更改 x 的值,而且由于 secObj 引用了同一个对象,所以 secObj.x 也会被更改。
这意味着在 javascript 中复制对象是行不通的。 因为更改一个对象的属性值会反映在另一个对象中,所以,我们如何创建对象的副本呢?
对象扩展
展开操作符... 是 ES6语言的新特征之一。 使用它来复制对象可能是复制对象最简单的方法。
let myObj = { name: 'James'}// copy myObj using spread syntaxlet secObj = {...myObj};myObj.name = 'John'console.log(secObj.name) // James
但是应该注意的是,这种复制对象的方法只能作用在一个嵌套级别上。 因此,spread 语法对对象进行一层复制。
let myObj = { name: 'James', school: { name: 'monef', class: 'ss3' }}let secObj = {...myObj};myObj.school.class = 'jss2'myObj.name = 'John';console.log(secObj) // {name:'James',school:{name:'monef',class:'jss2'}}
从上面的片段可以看出,虽然 secObj.name 的值即使在修改 myObj.name 之后也不会发生变化,但 myObj.school.class 中的变化导致了对象 secobj.school.class.so 的变化,而这个变化是有效的,它只是做了一个对象的浅拷贝。 如果确定对象的内容不是嵌套的,那么可以使用展开操作符。
Object.assign
在 MDN 上定义的 Object.assign 方法用于将可枚举属性的所有值从一个或多个源对象复制到目标对象。 看看下面的代码片段,
const target = { a: 1, b: 2 };const source = { b: 4, c: 5 };const returnedTarget = Object.assign(target, source);console.log(target); // { a: 1, b: 4, c: 5 }console.log(returnedTarget); //{ a: 1, b: 4, c: 5 }
分配复制源的内容并将其与目标的内容合并,将合并后的对象存储在目标中并返回,因此 returnedTarget 包含相同的对象。要使用这个对象复制对象,我们可以用对象{}替换目标,然后返回一个包含源对象所有值的新对象:
let myObj = { name: 'James'}let secObj = Object.assign({}, myObj);myObj.name = 'John';console.log(secObj.name); // James
如上面的代码片段所示,secObj 是使用 Object.assign 创建的,但使用一个空对象作为目标,将 myObj 中的所有属性复制到该对象中。
虽然这看起来是一个复制对象的智能解决方案,但它也做了对象的浅拷贝,因此,嵌套对象不会被复制,而是通过引用传递。
JSON.stringify and JSON.parse
上面提到的所有复制对象的方法都是对对象的浅拷贝。 把 JSON.stringify 和 JSON.parse 结合起来似乎可以做到深拷贝: JSON.stringify 将 javascript 对象转换为 JSON 字符串,而 JSON.parse 将 JSON 字符串转换为 javascript 对象。 可以智能地将这些组合起来,以提供可以对对象进行深度复制的解决方案。 如果我们 JSON.stringify 一个对象,它会变成一个新的字符串,并失去对其父对象的引用。 因此,解析这个新字符串将创建一个与其根失去所有联系的对象,因此,它是一个独立的对象。
let myObj = { name: 'James', school: { name: 'monef', class: 'ss3' }}let secObj = JSON.parse(JSON.stringify(myObj));myObj.school.class = 'jss2'myObj.name = 'okoro';console.log(secObj) // { name: 'James', school: { name: 'monef', class: 'ss3' } }
如上所述,即使更改了 myObj,secObj 的属性仍然保持不变。但是应该注意的是,如果源对象(在本例中为 myObj)包含一个方法,那么该方法将不会被复制。
let myObj = { name: 'James', school: { name: 'monef', class: 'ss3' }, dance() { console.log('dance') }}let secObj = JSON.parse(JSON.stringify(myObj));myObj.school.class = 'jss2'myObj.name = 'okoro';// secObj does not contain the dance function.console.log(secObj) // { name: 'James', school: { name: 'monef', class: 'ss3' } }
对于源对象中值为未定义或符号的属性,此方法也执行类似的操作。 这是因为 JSON.stringify 将对象转换为字符串,但这些对象不能表示为字符串。
使用第三方库或手动实现深拷贝方法
除了上面提到的方法,还有其他第三方库提供的解决方案,比如 lodash,underline。你也可以自己完成一个深拷贝方法,手写深拷贝方法经常在面试中考到。
以上就是我为大家总结的一些复制对象的方法,欢迎大家给我留言。