深拷贝与浅拷贝
想必你也有以下疑问:* 什么是拷贝(copy) ❓* 那深拷贝与浅拷贝什么 ❓* 如何实现深拷贝与浅拷贝呢 ❓* … ❓
接下来,就让我们一起去探索!深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
深拷贝
深拷贝: 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象(新旧对象不共享同一块内存),且修改新对象不会影响原对象(深拷贝采用了在堆内存中申请新的空间来存储数据,这样每个可以避免指针悬挂)
浅拷贝
浅拷贝: 如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址(新旧对象共享同一块内存),所以如果其中一个对象改变了这个地址,就会影响到另一个对象(只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃);那么此时我们会想到:浅拷贝和直接赋值难道不是一样的嘛❓有什么区别❓
赋值和浅拷贝的区别
- 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源
堆内存与栈内存的概念理解:

赋值和浅拷贝举例:
对比直接赋值和浅拷贝对象带来的改变有哪些❓
// 对象赋值
let obj1 = {name: 'Chen',age: 18,hobby: ['see a film', 'write the code', 'play basketball', 'tourism']
}
let obj2 = obj1;
obj2.name = 'Forever';
obj2.hobby[1] = 'swim';
obj2.hobby[2] = 'alpinism';
console.log('obj1===>', obj1);
console.log('obj2===>', obj2);

// 浅拷贝
let obj1 = {name: 'Chen',age: 18,hobby: ['see a film', 'write the code', 'play basketball', 'tourism']
}
let obj3 = {...obj1};
obj3.name = 'Forever';
obj3.hobby[1] = 'swim';
obj3.hobby[2] = 'alpinism';
console.log('obj1===>', obj1);
console.log('obj3===>', obj3);

上述例子,obj1是原数据,obj2是直接赋值得到的数据,obj3是通过浅拷贝得到的; 可清晰对比其对原数据的影响
对原始数据的影响 | |||
---|---|---|---|
– | 和原数据是否指向同一对象 | 第一层数据未基本数据类型 | 原数据包含子对象(引用数据类型) |
赋值 | 是 | 赋值后的数据改变,会使原数据一同改变 | 赋值后的数据改变,会使原数据一同改变 |
浅拷贝 | 否 | 浅拷贝后的数据改变,不会使原数据一同改变 | 赋值后的数据改变,会使原数据一同改变 |
浅拷贝的实现 注意:当拷贝对象只有一层的时候,是深拷贝
- 展开运算符…
// 展开运算符... 实现浅拷贝
let obj1 = {name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']
}
let obj2 = {...obj1};
obj2.hobby[1] = 'swim';
obj2.hobby[2] = 'alpinism';
obj2.name = 'Forever';
console.log('obj1===>', obj1); // obj1===> { name: 'Chen',hobby: [ 'see a film', 'swim','alpinism', 'tourism', name: 'Forever' ]}
console.log('obj2===>', obj2); // obj2===> { name: 'Chen',hobby: [ 'see a film', 'swim','alpinism', 'tourism', name: 'Forever' ]}
- Object.assign()
// Object.assign() 实现浅拷贝
let obj1 = {name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']
}
let obj2 = Object.assign({}, obj1);
obj2.hobby[1] = 'swim';
obj2.hobby[2] = 'alpinism';
obj2.name = 'Forever';
console.log('obj1===>', obj1); // obj1===>{ name: 'Chen',hobby: [ 'see a film', 'swim','alpinism', 'tourism', name: 'Forever' ]}
console.log('obj2===>', obj2); // obj2===> {name: 'Chen', hobby: [ 'see a film', 'swim','alpinism', 'tourism', name: 'Forever' ]}

当object只有一层的时候,是深拷贝;所以当原数据进行浅拷贝,改变obj2的name 原数据obj1中的name不会改变;
- Array.prototype.concat()
// Array.prototype.concat() 实现浅拷贝
let arr1 =[{name: 'Chen'},'see a film', 'write the code', 'play basketball', 'tourism'
];
let arr2 = arr1.concat([]);
arr2[0].name = 'Forever';
arr2[1] = 'play games';
console.log('arr1===>', arr1); // arr1===> [{ name: 'Forever' },'see a film','write the code','play basketball', 'tourism']
console.log('arr2===>', arr2); // arr2===> [{ name: 'Forever' },'play games','write the code', 'play basketball', 'tourism']
- Array.prototype.slice()
// Array.prototype.concat() 实现浅拷贝
let arr1 =[{name: 'Chen'},'see a film', 'write the code', 'play basketball', 'tourism'
];
let arr2 = arr1.slice();
arr2[0].name = 'Forever';
arr2[1] = 'play games';
console.log('arr1===>', arr1); // arr1===> [{ name: 'Forever' },'see a film','write the code','play basketball', 'tourism']
console.log('arr2===>', arr2); // arr2===> [{ name: 'Forever' },'play games','write the code', 'play basketball', 'tourism']

当Array只有一层的时候,是深拷贝;所以当原数据进行浅拷贝,改变arr2的arr[1],而原数据arr1中的arr1[1]没有改变;
深拷贝的实现
- JSON.parse(JSON.stringify())
//JSON.parse(JSON.stringify())实现深拷贝Object
let obj1 = {name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']
}
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1 === obj2); // false
obj2.name = 'Forever';
obj2.hobby[1] = 'swim';
obj2.hobby[2] = 'alpinism';
console.log('obj1===>', obj1); // obj1===> { name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']}
console.log('obj2===>', obj2); // obj2===> { name: 'Forever',hobby: ['see a film', 'swim', 'alpinism', 'tourism']}

//JSON.parse(JSON.stringify())实现深拷贝Array
let arr1 =[{name: 'Chen'},'see a film', 'write the code', 'play basketball', 'tourism'
];
let arr2 = JSON.parse(JSON.stringify(arr1));
console.log(arr1 === arr2); // false
arr2[0].name = 'Forever';
arr2[1] = 'play games';
console.log('arr1===>', arr1); // arr1===> [{ name: 'Chen' },'see a film','write the code','play basketball', 'tourism']
console.log('arr2===>', arr2); // arr2===> [{ name: 'Forever' },'play games','write the code', 'play basketball', 'tourism']

😻 既然Object和Array可以通过JSON.parse(JSON.stringify())实现深拷贝,那么Date与Function可以实现嘛? 我们一起尝试以下,看看会有什么情况出现:
let fun1 = function() {console.log('run~');
}
let fun2 = JSON.parse(JSON.stringify(fun1)) // undefined
JSON.parse(fun2) //Error: "undefined" is not valid JSON
let date1 = new Date();
let date2 = JSON.stringify(date1) // undefined
JSON.parse(date2) // Error: "undefined" is not valid JSON

JSON.parse() 方法用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象 因此undefined不能被转换并抛出异常:"undefined"不是有效的 JSON;
**为什么function类型与Date类型转换成JSON后 会是undefined, 请参考链接:MDN(感兴趣的可以根据JSON序列化原理,手动实现一下JSON.stringify())
我们平时开发中将JSON.stringify应用最多的可能就是浅层的对象进行深拷贝,也就是进行序列化处理。但是当我们进行手撕代码的时候,需要考虑各种边界情况,这对于我们来说就比较麻烦,作为面试也是对数据类型的全面考察
- jQuery.extend()方法
//需要引入jQuery库哦~
let obj = {name: 'Chen',hobby: [ 'see a film', 'write the code', 'play basketball', 'tourism']
}
let obj1 = jQuery.extend(true, {}, obj);
console.log(obj === obj1); // false
obj2.name = 'Forever';
obj2.hobby[1] = 'swim';
obj2.hobby[2] = 'alpinism';
console.log('obj1===>', obj1); // obj1===> { name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']}
console.log('obj2===>', obj2); // obj1===> { name: 'Chen',hobby: ['see a film', 'swim', 'alpinism', 'tourism']}

- 手写递归方法:(递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝)
// 检测数据类型的功能函数
const checkedType = (target) => Object.prototype.toString.call(target).replace(/\[object (\w+)\]/, "$1").toLowerCase();
// 实现深拷贝(Object/Array)
const clone = (target) => {let result;let type = checkedType(target);if(type === 'object') result = {};else if(type === 'array') result = [];elsereturn target;for (let key in target) {if(checkedType(target[key]) === 'object' || checkedType(target[key]) === 'array') {result[key] = clone(target[key]);} else {result[key] = target[key]; }}return result;
}
调用一下手写递归实现深拷贝方法:🙊
const obj = {name: 'Chen',detail: {age: '18',height: '180',bodyWeight: '68'},hobby: ['see a film','write the code','play basketball', 'tourism']
}
const obj1 = clone(obj);
console.log(obj1); // { name: 'Chen',detail: { age: '18', height: '180', bodyWeight: '68' },hobby: [ 'see a film', 'write the code', 'play basketball', 'tourism' ]}
console.log(obj1 === obj); // false
至此 深拷贝与浅拷贝就结束咯~
是我对js拷贝的理解了,在闲暇时光做的一个总结归纳~> 想不到自己还是写完了~> 希望对你有所帮助~
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享