面试题 -- JS 深浅拷贝?
# 前言
浅拷贝: 只拷贝当前一层,更深层次的对象,只拷贝引用(内存地址),不拷贝数据。 深拷贝: 拷贝多层,该对象每一级的数据都会拷贝,而不是拷贝引用。
常见现象
- 基本数据类型的赋值,赋值成功后数据被拷贝,之后互不影响,是 深拷贝 的一种,如以下代码。
let a = 1;
let b = a;
b = 2
console.log(a, b); // 1 2
- 数组和对象的赋值,赋值成功后,两者互相影响,是 浅拷贝 的一种,如以下代码。
let arr = [1, 2, 3];
let newArr = arr;
newArr.push(4);
console.log(arr, nweArr); // [1, 2, 3, 4] [1, 2, 3, 4]
*该问题在前后端交互时,比较常见
# 对象 浅拷贝
浅拷贝 (opens new window)只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存, 所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
# 方法一:对象的赋值
以下代码,不管改变哪个对象,两个对象都会共享一个结果。
let obj = {
name: 'Tom',
age: 21,
other: {
desc: 'day day up!'
}
}
let newObj = obj
# 方法二:遍历赋值
以下代码,通过 for in 遍历赋值,只会拷贝第一层数据,像 other.desc 不会被拷贝数据,而是拷贝内存指针,所以 name 不会变化,而 other.desc 会更新为新的值。
let obj = {
name: 'Tom',
age: 21,
other: {
desc: 'day day up!'
}
}
let newObj = {}
for(let i in obj) {
newObj[i] = obj[i]
}
newObj.name = 'Rose'
newObj.other.desc = 'good goog study!'
console.log(obj, newObj)

# 方法三:Object.assign()
Object.assign(target, sources) (opens new window) 方法将所有可枚举(Object.propertyIsEnumerable() 返回 true)和自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。
target:目标对象,接收源对象属性的对象,也是修改后的返回值。
sources:源对象,包含将被合并的属性。
let obj = {
name: 'Tom',
age: 21,
other: {
desc: 'day day up!'
}
}
let newObj = {}
Object.assign(newObj, obj)
newObj.name = 'Rose'
newObj.other.desc = 'good goog study!'
console.log(obj, newObj)

# 方法四:扩展运算符
let obj = {
name: 'Tom',
age: 21,
other: {
desc: 'day day up!'
}
}
let newObj = {...obj}
newObj.name = 'Rose'
newObj.other.desc = 'good goog study!'
console.log(obj, newObj)

# 数组 浅拷贝
# 方法一:数组的赋值
let arr = [1, 2, {name: 'Tom'}]
let newArr = arr
console.log(arr, newArr)

# 方法二:扩展运算符
let arr = [1, 2, {name: 'Tom'}]
let newArr = [...arr]
console.log(arr, newArr)

# 方法三:slice()
slice() (opens new window)方法将数组的一部分的浅拷贝并返回到一个新的数组对象中,原始数组不会被修改。
let arr = [1, 2, {name: 'Tom'}]
let newArr = arr.slice(0)
console.log(arr, newArr)

# 方法四:map
map (opens new window)方法创建一个新数组,其中填充了对调用数组中的每个元素调用提供的函数的结果,原始数组不会被修改。
let arr = [1, 2, {name: 'Tom'}]
let newArr = arr.map(item => item)
console.log(arr, newArr)

# 深拷贝
深拷贝 (opens new window)对象的深拷贝是一个副本,其属性不共享与创建副本的源对象相同的引用(指向相同的底层值)。因此,当您更改源或副本时,您可以确保不会导致其他对象也发生更改;也就是说,您不会无意中对源或副本造成您不期望的更改。这种行为与浅拷贝的行为形成对比,其中对源或副本的更改也可能导致另一个对象也发生更改(因为两个对象共享相同的引用)。
# 方法一:解构赋值
- 针对一维数组或对象,是 深拷贝。
let arr = [1, 2, 3]
let newArr = [...arr]
newArr.push(4)
console.log(arr, newArr)

- 针对多维数组或对象,是 浅拷贝。
let arr = [[1, 2, 3], [4, 5]]
let newArr = [...arr]
newArr[0].push(10)
console.log(arr, newArr)

# 方法二:JSON.parse(JSON.stringify())
JSON.parse(JSON.stringify()) (opens new window)对 JavaScript 对象进行深度复制的一种方法(如果可以序列化)是 JSON.stringify() (opens new window) 将对象转换为 JSON 字符串,然后 JSON.parse() (opens new window)将字符串转换回(全新的)JavaScript 对象。
let obj = {
name: 'Tom',
age: Infinity,
other: {
desc: new Date()
}
}
let newObj = JSON.parse(JSON.stringify(obj))
console.log(obj, newObj)

缺点
不可以拷贝 undefined,function,RegExp 正则表达式类型
# 方法三:递归
(目前大部分团队)手写一个 deepClone() 工具类。
实现方式参考递归函数实现深拷贝 (opens new window)