推荐答案
浅拷贝和深拷贝都是在复制对象或数组时使用的概念,区别在于它们复制的深度:
浅拷贝(Shallow Copy):创建一个新的对象或数组,但它仅仅复制原始对象或数组的第一层属性的值。如果属性值是基本数据类型(如数字、字符串、布尔值等),则直接复制其值;如果属性值是引用数据类型(如对象、数组等),则复制的是其引用地址。这意味着新对象或数组的引用类型属性与原始对象或数组共享同一块内存空间。修改其中一个的引用类型属性,另一个也会受到影响。
深拷贝(Deep Copy):创建一个新的对象或数组,它会递归地复制原始对象或数组的所有层级的属性值。这意味着新对象或数组和原始对象或数组完全独立,各自拥有自己的内存空间。修改其中一个的属性,不会影响到另一个。
简单来说,浅拷贝只复制“表面”,深拷贝复制“所有”。浅拷贝速度快,但可能产生修改互相影响的副作用;深拷贝速度相对较慢,但能保证数据独立性。
本题详细解读
浅拷贝的实现方式
浅拷贝的实现方式有很多种,常见的包括:
Object.assign(): 将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象。它只会复制属性值,如果属性值是引用类型,则复制的是引用。
const original = { a: 1, b: { c: 2 } }; const shallowCopy = Object.assign({}, original); shallowCopy.a = 3; // 修改 shallowCopy 的基本类型属性 shallowCopy.b.c = 4; // 修改 shallowCopy 的引用类型属性 console.log(original); // 输出:{ a: 1, b: { c: 4 } } ,original 被修改了 console.log(shallowCopy); // 输出:{ a: 3, b: { c: 4 } }
展开运算符(...): 可以用于对象和数组的浅拷贝。
-- -------------------- ---- ------- ----- -------- - - -- -- -- - -- - - -- ----- ----------- - - ----------- -- ------------- - -- --------------- - -- ---------------------- -- ---- -- -- -- - -- - - - --------- ---- ------------------------- -- ---- -- -- -- - -- - - - ----- ------------- - --- --- ---- ----- ---------------- - ------------------- ------------------- - -- ---------------------- - -- --------------------------- -- --- --- --- --- - ------------- ---- ------------------------------ -- ------ --- ---
Array.slice(): 不带参数时,可以用于浅拷贝数组。
const originalArray = [1, [2, 3]]; const shallowCopyArray = originalArray.slice(); shallowCopyArray[0] = 4; shallowCopyArray[1][0] = 5; console.log(originalArray); // 输出: [1, [5, 3]] , originalArray 被修改了 console.log(shallowCopyArray); // 输出: [4, [5, 3]]
Array.concat():不带参数时,也可以用于浅拷贝数组。
const originalArray = [1, [2, 3]]; const shallowCopyArray = originalArray.concat(); shallowCopyArray[0] = 4; shallowCopyArray[1][0] = 5; console.log(originalArray); // 输出: [1, [5, 3]] , originalArray 被修改了 console.log(shallowCopyArray); // 输出: [4, [5, 3]]
深拷贝的实现方式
深拷贝的实现需要递归地复制属性,常见的实现方式包括:
JSON.parse(JSON.stringify()): 一种简单但有局限性的深拷贝方法。它首先将对象或数组转换为 JSON 字符串,然后再将字符串解析为新的对象或数组。这种方法可以处理大部分情况,但对于一些特殊情况(如函数、循环引用、Date 对象等)可能会失效或产生意料之外的结果。
const original = { a: 1, b: { c: 2 }, d: function(){}, e: new Date()}; const deepCopy = JSON.parse(JSON.stringify(original)); deepCopy.a = 3; deepCopy.b.c = 4; console.log(original); // 输出:{ a: 1, b: { c: 2 }, d: function(){}, e: Thu Aug 10 2023 17:18:48 GMT+0800 (中国标准时间) } ,original 未被修改 console.log(deepCopy); // 输出:{ a: 3, b: { c: 4 }, e: "2023-08-10T09:18:48.784Z" },函数d 消失,Date e 转换成字符串
递归实现: 自定义一个函数,递归地遍历对象或数组,创建新的对象或数组,并复制每一个属性值。这种方法可以处理更复杂的情况,但需要考虑循环引用的情况,否则会导致栈溢出。
-- -------------------- ---- ------- -------- ------------- ----- - --- ---------- - -- ---- --- ---- -- ------ --- --- --------- - ------ ---- -- -------- - -- ---------------- - ------ --------------- -- ------ - ----- ---- - ------------------ - -- - --- -------------- ------ -- ----- --- ---- --- -- ---- - -- ------------------------- - --------- - ------------------ ------- -- ---- - - ------ ----- - ----- -------- - - -- -- -- - -- - -- -- ------------- -- --- ------ -- ----- ----------- - ------------------- ------------- - -- --------------- - -- ---------------------- -- ---- -- -- -- - -- - -- -- ------------- -- --- --- -- ---- -------- -------- -------- - ------------------------ -- ---- -- -- -- - -- - -- -- ------------- -- --- --- -- ---- -------- -------- -------- -
如何选择
在实际开发中,选择使用浅拷贝还是深拷贝取决于具体的需求:
- 如果只需要复制对象或数组的第一层属性,且不需要修改原始数据,可以使用浅拷贝。
- 如果需要完全独立的新对象或数组,修改新对象或数组不应该影响到原始数据,就需要使用深拷贝。
- 在需要深拷贝时,如果对象结构简单,没有特殊类型,可以使用
JSON.parse(JSON.stringify())
。 - 如果对象结构复杂,包含函数、循环引用等,需要使用递归实现的深拷贝或者使用第三方库lodash的cloneDeep等方法。
理解浅拷贝和深拷贝的区别,有助于在开发过程中避免不必要的bug,并更好地管理数据。