深拷贝和浅拷贝

前言

深拷贝和浅拷贝的区别,各位大小牛们应该都不陌生,这里主要给前端新人介绍一下,也帮助我自己复习一下。

为什么会突然讲这个呢,最近搬砖有点头晕目眩,趁着有空闲时刻,突然想练习一下递归,看看自己尚能饭否,结果就猪脑过载了。

后来就遇到了深拷贝,毕竟作为一道前端入门的经典面试题,以及和递归相关的题目,出场率也是挺高的。

复制一个对象

在 javascript 里是不能用变量赋值的方式来复制一个对象。因为 javascript 的赋值是值传递,复制对象的引用值到另一个变量,最终会导致 2 个变量都指向同一个对象。

浅拷贝

浅拷贝创建一个新对象,将旧对象的属性依次复制给新对象。

属性值是赋值过去的,如果碰到属性值为对象类型时,则只复制引用值,最终导致新旧对象的同名属性都指向同一个对象。

因此我们通常戏称浅拷贝只复制对象的第一层属性。

我自己写的哦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function shallow_copy(src) {
//普通类型或函数直接返回
if (typeof src !== 'object') {
return src
}

// 借用 源对象 构造器,创建相同类型的新对象
const constructor = Object.getPrototypeOf(src).constructor
const dst = new constructor()

// 遍历属性然后复制
Object.keys(src).forEach(k => {
dst[k] = src[k]
})
return dst
}

深拷贝

深拷贝创建一个新对象,将旧对象的属性依次复制给新对象。当中遇到对象类型的属性,则创建新的子对象,将旧的子对象的属性复制给新的子对象。

这里区别在于碰到属性值是对象类型时,我们不是简单复制引用值了,而是在内存中创建新对象,然后再进行拷贝。

这种将子孙对象也进行拷贝的做法,就很“深”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deep_copy(src) {
//普通类型或函数直接返回
if (typeof src !== 'object') {
return src
}

// 借用源对象构造器,创建相同类型的新对象
const constructor = Object.getPrototypeOf(src).constructor
const dst = new constructor()

// 递归:普通数据类型会直接返回,对象或数组则继续遍历复制
Object.keys(src).forEach(k => {
dst[k] = deep_copy(src[k])
})

return dst
}

注意事项

在遍历对象的时候我使用了 Object.keys(),该方法会返回对象自身可枚举属性的集合。

但是有小伙伴可能会使用 for in 来遍历对象的属性,这里就要讲一下区别:

- `Object.keys()` 只会枚举自身属性
- `for in` 不仅会枚举自身属性,还会枚举原型链上的属性

如果自己写一个 Object.keys() 方法,我可能会这样写

1
2
3
4
5
6
7
8
9
Object.keys = function (obj) {
let result = []
for (let k in obj) {
if (obj.hasOwnProperty(k) && obj.propertyIsEnumerable(k)) {
result.push(k)
}
}
return result
}

可以看到我在实现 Object.keys() 时,也使用了 for in,但是注意,只有自身属性才会被加入集合。

关于 Object.prototype.propertyIsEnumerable 其实只是单纯为了复习一下方法,在这里没有任何作用,因为上 for in 并不能遍历出不可枚举的属性。