让面试官眼前一亮的浅拷贝和深拷贝(js的拷贝详解)
前言
在讨论“拷贝”的概念时,我们通常指的是在计算机科学和编程领域中,将数据从一个位置复制到另一个位置的过程。它分为好几种类型,如拷贝的深浅(浅拷贝和深拷贝)、引用拷贝等。对于刚接触js这门语言的小伙伴来说,复制一个对象可能没有什么头绪,所以接下来我们将分析,在js内如何去对一个对象进行复制的。
拷贝类型
- 浅拷贝(Shallow Copy)
浅拷贝是指创建一个新对象,这个新对象具有与原始对象相同的数据,但对原始对象的任何引用类型属性,它只复制了引用。如果原始对象包含其他对象(如数组、对象等),那么浅拷贝后的新对象将包含指向原始对象中那些对象的相同引用。因此,修改任何共享对象会影响到所有指向它的拷贝。就像下面这样:
let obj = {
age: 18
}
let obj2 = obj
obj.age = 20 // obj2.age = 20 ,不管修改哪一个,这两个对象都会收影响。
console.log(obj.age) // 20
- 深拷贝(Deep Copy)
深拷贝会在堆内存的创建一个完全独立的新对象,并递归地复制对象内的所有子对象。这样,即使原始对象中的数据发生变化,也不会影响到深拷贝后的对象。
let obj = {
a: 1,
b: {n: {c: 1}},
}
let newObj = JSON.parse(JSON.stringify(obj))
obj.b.n.c = 2
console.log(newObj.b.n.c); // 输出 1
浅拷贝和深拷贝如何实现
-
浅拷贝(Shallow Copy)
浅拷贝实现方式:
- 使用
Object.assign()
方法:Object.assign()
方法用于将所有可枚举的属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
const obj = { a: 1, b: { c: 2 }}; const shallowCopy = Object.assign({}, obj); obj.a = 5 obj.b.c = 4 console.log(shallowCopy) // { a: 1, b: { c: 4 } }
- 使用扩展运算符 (
...
) : 它是ES6(ECMAScript 2015)引入的一个新特性,它允许将一个数组或类数组对象的元素展开,也可以用于函数调用时传入参数,以及在对象字面量中合并多个对象的属性。
// 拷贝对象 const obj = {name: 'lisi', friend: { name: 'wangwu'}}; const shallowCopy = {...obj}; obj.name = 'fff' obj.friend.name = 'laoliu' console.log(shallowCopy) // { name: 'lisi', friend: { name: 'laoliu' } }
// 拷贝数组 const arr = [1, 2, [3, 4]]; const shallowCopy = [...arr]; arr[0] = 2 arr[1] = 3 arr[2][0] = 1 arr[2][1] = 2 console.log(shallowCopy) // [ 1, 2, [ 1, 2 ] ]
- 使用数组的concat()方法:对复制数组有效,它是数组对象原型上的一个数组元素拼接的方法,将参数数组拼接到调用方法的数组上。
const arr = [1, 2, [3, 4]]; const shallowCopy = [].concat(arr); arr[0] = 2 arr[1] = 3 arr[2][0] = 1 arr[2][1] = 2 console.log(shallowCopy) // [ 1, 2, [ 1, 2 ] ]
- 使用数组的slice()方法:
arr.slice(0)
是在JavaScript中用于复制数组的一种常见方式。slice()
方法返回一个新的数组,包含从开始到结束(不包括结束)的数组的一部分浅拷贝,原始数组不会被改变。
const arr = [1, 2, {a: 5}]; const shallowCopy = arr.slice(0); arr[0] = 2 arr[1] = 3 arr[2].a = 6 console.log(shallowCopy) // [ 1, 2, { a: 6 } ]
- 使用库函数: 有许多第三方库提供了浅拷贝功能,如 lodash 的
_.clone()
函数。
const _ = require('lodash'); const originalObj = { a: 1, b: { c: 2 } }; const deepCopy = _.clone(originalObj);
- 使用
-
深拷贝(Deep Copy)
深拷贝实现方式:
- 使用 JSON 的
stringify
和parse
方法:
JSON.stringify()
是 JavaScript 中的一个全局函数,用于将一个 JavaScript 值(通常是对象或数组)转换为一个 JSON 字符串。JSON.parse()
是 JavaScript 中的一个全局函数,用于将一个 JSON 格式的字符串解析成 JavaScript 对象或值。const originalObj = { a: 1, b: { c: 2 } }; const deepCopy = JSON.parse(JSON.stringify(originalObj));
注意:这种方法有一些限制,如无法处理函数和循环引用,且会忽略原型链上的属性。
总结:
- 无法识别bigInt类型。
- 无法拷贝 undefined,function,Symbol。
- 无法处理循环引用。
- 使用 JSON 的
-
使用库函数: 有许多第三方库提供了深拷贝功能,如 lodash 的
_.cloneDeep()
函数。const _ = require('lodash'); const originalObj = { a: 1, b: { c: 2 } }; const deepCopy = _.cloneDeep(originalObj);
-
使用structuredClone()方法:它是一种新的方法,用于创建一个对象或数组的深度克隆,包括其所有可枚举和不可枚举的属性,以及处理循环引用和特殊类型的值(如
Map
,Set
,Date
,RegExp
等)。let obj = { a: 1, b: {n: 2} } const newObj = structuredClone(obj) obj.b.n = 3 console.log(newObj) // { a: 1, b: { n: 2 } }
手写浅拷贝和深拷贝
-
浅拷贝: 利用循环遍历属性,这个方法也是浅拷贝的实现原理,他主要是去获取原对象的所有属性,将其赋值给新对象的属性上。
function shallowCopy(obj){ let obj1 = {} for(let key in obj){ //对象内置hasOwnProperty方法用于检查一个对象是否具有指定的自身属性(而非继承来的属性) if(obj.hasOwnProperty(key)){ obj1[key] = obj[key]; } } return obj1 } let obj = { a: 1, b: {n: 2} } obj.a = 2; obj.b.n = 3 ; console.log(shallowCopy(obj)) //{ a: 2, b: { n: 3 } }
-
深拷贝: 该方法为深拷贝的实现原理,也是利用循环遍历属性,当我们需要手动写一个深拷贝时,我们可以像下面这样使用递归的方式,去深层次遍历拷贝对象的属性,直到找到所有的基本类型数据后,全部放入新对象的属性内,该新对象会在堆内存中开辟一个新空间,且属性值与拷贝对象的值完全一致,并不受其影响。
let obj = { a: 1, b: {n: 2} } function deepClone(obj) { // 简洁版 if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]); } } return clone; } let obj2 = deepClone(obj) obj.b.n = 20 console.log(obj2) // { a: 1, b: { n: 2 } }
总结
转载自:https://juejin.cn/post/7380296247720935459