本系列面试题旨在学会相关知识点,从而轻松应对面试题的各种形式,本文讲解了 JavaScript 中拷贝的相关知识,以及如何手写深浅拷贝。
感觉有帮助的小伙伴请点赞👍鼓励一下 ~
什么是拷贝
拷贝其实就是复制,很多场景需要我们复制一份数据出来,然后对复制后的数据进行操作,可能要求不影响原数据,也可能会要求和原数据产生一些联动。所以根据深拷贝和浅拷贝的功能,就可以满足上述两种要求。
值类型的拷贝
值类型其实没有深浅拷贝之分,亦可以说值类型都是深拷贝。因为值类型拷贝后的值,不会跟原数据产生任何联动,修改拷贝后的值,原数据不会产生任何变化。
let a = 1
let b = a
b = 2
console.log(a)
console.log(b)
浅拷贝
重新在堆中创建内存,拷贝前后的基本类型互不影响,拷贝前后的引用类型还是会共享同一块内存,故而会相互影响。
先写一个例子来看一下:
function shallowCopy(obj) {
const cloneObj = {}
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
cloneObj[i] = obj[i]
}
}
return cloneObj
}
let person = {
name: "张三",
hobbies: ["吃饭", "睡觉", "打豆豆"]
}
let person1 = shallowCopy(person)
person1.name = '李四'
person1.hobbies[0] = '美女'
console.log(person);
console.log(person1);
可以看到我们修改 person1 的值类型属性 name ,并没有影响到 person 的 name ,但是我们修改引用类型 hobbies 的时候,person 也随着 person1 改变了,这就是浅拷贝。
除了上面这一种,浅拷贝的实现方式还有 Object.assign() , 展开运算符... , array.slice() array.concat() 。
深拷贝
从堆内存中开辟一块新的区域存放新对象,对原始对象的所有属性进行递归拷贝,对所有的引用类型的属性同样开辟新区域,修改新对象不会影响原对象。
从上面的浅拷贝的例子中可以看出,person 的 hobbies 虽然是个引用类型,但是 hobbies 的每一个元素都是一个字符串,也就是值类型,所以我们只要再次对 hobbies 进行浅拷贝,那么 hobbies 也就会互不影响了。 所以我们可以得出一个结论,只要对一个对象无限递归进行浅拷贝,最终的结果就是一个深拷贝。
递归浅拷贝
代码如下,要考虑到种种特殊情况。
function deepClone(obj) {
const cloneObj = new obj.constructor()
if (obj === null) return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
if (typeof obj !== 'object') return obj
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
cloneObj[i] = deepClone(obj[i])
}
}
return cloneObj
}
let person = {
name: "张三",
hobbies: ["吃饭", "睡觉", "打豆豆"]
}
let person1 = deepClone(person)
person1.name = '李四'
person1.hobbies[0] = '美女'
console.log(person);
console.log(person1);
结果如下,现在只有李四喜欢美女了。说明我们的深拷贝就成功了。
JSON.parse(JSON.stringify())
除了上面递归浅拷贝的方式来实现深拷贝之外,还可以使用 JSON.parse(JSON.stringify()) 来达到相同的结果。
但是这种方式有它的弊端,我们来看一下
let person = {
name: "张三",
hobbies: ["吃饭", "睡觉", "打豆豆"],
date: new Date,
fuc: () => { },
reg: /w/
}
const person1 = JSON.parse(JSON.stringify(person));
person1.name = '李四'
person1.hobbies[0] = '美女'
console.log(person);
console.log(person1);
来看一下结果,date 属性从一个对象变成了一个字符串,fuc 属性消失了,reg 属性变成了空对象。所以说这种方式弊端是很大的,一不小心就会有意外产生。
undefined 、任意的函数以及 symbol 值,在序列化过程中会被忽略;Date 日期会被当做字符串处理;NaN 和 Infinity 格式的数值及 null 都会被当做 null ;- 其他类型的对象,包括
Map/Set/WeakMap/WeakSet ,仅会序列化可枚举的属性; - 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误;
我们再使用递归浅拷贝的方式来看一下结果,简直是一模一样。
|