JS 中的浅拷贝和深拷贝

98 阅读4分钟

前言

笔者最近在准备面试考题的时候,无意间看到了是否了解 JS 中的浅拷贝以及深拷贝,后来仔细想了一下发现还真的不太清楚完整的运行原理,所以决定写一篇文章来厘清一下自己的观念

浅拷贝 V.S 深拷贝

在开始正式进入文章主题之前,笔者先来简单阐述一下什么是浅拷贝什么是深拷贝,看上面那张图就知道,浅拷贝就是共用同一个内存空间,而深拷贝则是两个不同的内存空间,以观念来说非常的简单,但这也是 JS 最多地坑的地方

为什么会这么说呢?接下来就用几个简单的小题目带大家了解 JS 对于浅拷贝以及深拷贝的操作吧!

浅拷贝(shallow copy)

先来小试身手一下,底下有一段代码,大家在贴到 dev tool 上面执行之前,不妨先想想看会输出什么值。

let obj1 = {a: 1}
let obj2 = obj1
obj1.a = 2
console.log(obj2.a)  // ?

先不要以 JS 的逻辑去看,依照正常的程序运行原理应该会输出 1 才对,因为方法运行的原理是值传递,但 JS 不一样,JS 对于对象的操作是引用传递,所以上面代码中的 obj1 以及 obj2 其实是共用同一个内存,既然是共用同一个内存想当然只要有一方的值改变了另一方也会跟着改变,这个就叫做浅拷贝。

在回到刚刚的例子,聪明的大家应该会想到另一个做法,我就直接创键出一个新的对象,这样两个对象就不会影响到彼此啦!

let obj1 = {a: 1}
let obj2 = {a: obj1.a}
obj1.a = 2
console.log(obj2.a)  // 1

乍看之下好像真的是两个不同的对象,的确在这种情况下是一种深拷贝,但在某些情况下他其实还是属于一种浅拷贝,笔者就曾经因为这样被坑很多次

如果我今天稍微改变一下 obj1 的架构,变成这样:

let obj1 = {a: {a: 1}}
let obj2 = {a: obj1.a}
obj1.a.a = 2
console.log(obj2.a)   // 输出{a: 2}而不是{a: 1}

这时候 obj2 内的值也变了,所以我们可以发现,只要对象超过一层,这种作法只会复制表层而已,深层的内容还是共用同一个内存,因此两者还是会互相影响。

shallow copy

常见的小陷阱

  • … operator

相信大家都知道 ES6 有一个新的 operator,利用 . . .array 或 …obj 的方式达到展开(spread) 的效果,我们可以把刚刚的例子用这个 operator 来达到更简单的写法。

let obj1 = {a: 1}
let obj2 = {...obj1}
obj1.a = 2
console.log(obj2.a)   // 1

完美,看起来又是一种深拷贝了,但其实这个也不算是深拷贝,这个跟刚刚的例子一样只要稍微变化一下他又变成浅拷贝了。

let obj1 = {a: {a: 1}}
let obj2 = {...obj1}
obj1.a.a = 2
console.log(obj2.a)  // {a: 2}
  • Object.assign(target, source)

简单来说就是将来源的 object 分派给指定的对象。

let obj1 = {a: 1}
let obj2 = Object.assign({}, obj1)
obj1.a = 2
console.log(obj2.a)  // 1

这个也是在 ES6 推出的对象操作方法,虽然上面的 . . .operator 是浅拷贝,但笔者决定再给 ES6 一个机会,所以没意外应该就是深拷贝了吧!但其实这个稍微变化一下又是浅拷贝了。

let obj1 = {a: {a: 1}}
let obj2 = Object.assign({}, obj1)
obj1.a.a = 2
console.log(obj2.a)  // {a: 2}

所以你说 JS 是不是在搞各位开发者,ES6 自己推出的两个 method 都隐藏了非常多的地坑等着大家去踩,俗话说的好:前人踩坑,后人乘凉,希望大家看完这篇文章之后不会再踩到一样的地坑了

深拷贝(deep copy)

讲了这么多浅拷贝,接下来终于要来讲深拷贝的操作了,看了上面这么多例子应该就发现不能用一些简单的方法来进行深拷贝了,笔者这边要来介绍几个深拷贝的操作。

  • JSON.stringify(obj) 以及 JSON.parse(JSONString)

如果想要真的进行深拷贝的话,就回归最基本的 JSON 操作吧

将对象转成字符串再转成对象,这样就真的可以确保出来的会是一个新的对象而且是使用不同的内存。

let obj1 = {a: {a: 1}}
let obj2 = JSON.parse(JSON.stringify(obj1))
obj1.a.a = 2
console.log(obj2.a)  // {a: 1}
  • lodash 内的 cloneDeep

lodash 是 JS 中一套非常强大的 utility library,里面有一个 API 叫 _.cloneDeep(obj) ,这个就会返回一个利用深拷贝而得到的新对象,这个方法也是比较简单而且看起来也比较干净的作法。

let obj1 = {a: {a: 1}}
let obj2 = _.cloneDeep(obj1)
obj1.a.a = 2
console.log(obj2.a)  // {a: 1}

总结

讲了这么多不晓得大家对于浅拷贝以及深拷贝有没有更深入的了解了。如果看完文章有任何问题或是建议都欢迎在下方的留言区留言给我。