面试题 -- JS 深浅拷贝?

Ray Shine 2022/6/16 JavaScript

# 前言

浅拷贝: 只拷贝当前一层,更深层次的对象,只拷贝引用(内存地址),不拷贝数据。 深拷贝: 拷贝多层,该对象每一级的数据都会拷贝,而不是拷贝引用。

常见现象

  1. 基本数据类型的赋值,赋值成功后数据被拷贝,之后互不影响,是 深拷贝 的一种,如以下代码。
let a = 1;
let b = a;
b = 2
console.log(a, b);   // 1   2
  1. 数组和对象的赋值,赋值成功后,两者互相影响,是 浅拷贝 的一种,如以下代码。
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)对象的深拷贝是一个副本,其属性不共享与创建副本的源对象相同的引用(指向相同的底层值)。因此,当您更改源或副本时,您可以确保不会导致其他对象也发生更改;也就是说,您不会无意中对源或副本造成您不期望的更改。这种行为与浅拷贝的行为形成对比,其中对源或副本的更改也可能导致另一个对象也发生更改(因为两个对象共享相同的引用)。

# 方法一:解构赋值

  1. 针对一维数组或对象,是 深拷贝
let arr = [1, 2, 3]
let newArr = [...arr]
newArr.push(4)
console.log(arr, newArr)

解构赋值一维数组

  1. 针对多维数组或对象,是 浅拷贝
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)

深拷贝序列化

缺点

不可以拷贝 undefinedfunctionRegExp 正则表达式类型

# 方法三:递归

(目前大部分团队)手写一个 deepClone() 工具类。 实现方式参考递归函数实现深拷贝 (opens new window)

最后更新时间: 2025/8/27 00:23:57
ON THIS PAGE