为了方便知识的复习和查看,将其记录下来,一起努力吧!
涉及的面试题:什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?
在说深浅拷贝之前,我们需要了解一下不同数据类型的变量的存储方式。
我们都知道js的数据类型分为两大类:
-
基本数据类型
number 、string、 boolean、 undefined、 null、 symbol
-
引用数据类型
object(Array/Rex)
基本数据类型保存在栈内存,引用类型保存在堆内存中。根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是可以把它的地址写在栈内存中以供我们访问。
let eg1 = {
num:1,
obj: {
name:'gxm',
age:18
}
}
这个eg1
对象里的属性的储存情况如下:
浅拷贝
什么是浅拷贝?
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。所以原对象和拷贝后的对象是相互影响的。
如何实现浅拷贝?
(1)通过 Object.assign
来解决问题。
let real = {
str: '我是本体',
obj: {
name:'gxm',
age:18
}
}
let clone = Object.assign({}, real)
clone.str = '我克隆了';
console.log(real.str); //我是本体
console.log(clone.str); //我克隆了
clone.obj.age = 16 ;
console.log(real.obj.age); //16
console.log(clone.obj.age); //16
通过这个例子,我们也可以看出Object.assign()
只会拷贝所有属性值到新的对象中,如果属性值是对象的话,拷贝的是地址。所以Object.assign
不是深拷贝。
(2)通过展开运算符(…)来解决
let a = {
age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就需要使用到深拷贝了
let real = {
str: '我是本体',
obj: {
name:'gxm',
age:18
}
}
let clone = {...real}
clone.str = '我克隆了';
console.log(real.str); //我是本体
console.log(clone.str); //我克隆了
clone.obj.age = 16 ;
console.log(real.obj.age); //16
console.log(clone.obj.age); //16
浅拷贝只能解决了第一层的问题,如果接下去的值中还有对象的话,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝。
深拷贝
什么深拷贝?
深拷贝就是拷贝多层,嵌套的对象也会被拷贝出来,相当于开辟一个新的内存地址用于存放拷贝的对象。
我的理解深拷贝就是完全拷贝,且原对象和拷贝后的对象没有任何关联。
如何实现深拷贝?
(1)使用 JSON.parse(JSON.stringify(object)) 深层拷贝
let realObj = {
name: 'gxm',
jobs: {
first: 'BAT'
}
}
let deepCloneObj = JSON.parse(JSON.stringify(realObj))
realObj.jobs.first = 'Google'
console.log(deepCloneObj.jobs.first) // BAT
但是该方法也是有局限性的:
- 会忽略
undefined
- 会忽略
symbol
- 不能序列化函数
- 不能解决循环引用的对象
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
如果你有这么一个循环引用对象,你会发现你不能通过该方法深拷贝。
(2)MessageChannel
深层拷贝
MessageChannel
深层拷贝可以用在含有有undefined
和循环引用的场景。
// 有undefined + 循环引用
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
f: undefined
}
obj.c = obj.b;
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
function deepCopy(obj) {
return new Promise((resolve) => {
const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
}
deepCopy(obj).then((copy) => { // 请记住`MessageChannel`是异步的这个前提!
let copyObj = copy;
console.log(copyObj, obj)
console.log(copyObj == obj)
});
运行结果如下: 但拷贝有函数对象时,还是会报错。
(3)自实现深拷贝
eg1: 这个例子可以实现基本数据类型、引用对象类型和对象里嵌套对象的深层拷贝,但不适用于循环引用。所谓循环引用就是自己调用自己。
function deepClone(obj) {
//obj是null的情况
if(obj == undefined){
return obj;
}
//obj是基本数据类型的情况
if(typeof obj !== 'object'){//此处的object一定要小写
return obj;
}
//正则、时间
if(obj instanceof RegExp){
return new RegExp(obj);
}
if(obj instanceof Date){
return new Date(obj)
}
//如果obj是的数组或对象时
let cloneObj = new obj.constructor;//获得obj的构造函数
for(let key in obj ){
if(obj.hasOwnProperty(key)){
// cloneObj[key] = obj[key];
//这种情况是obj[key]不是引用类型的值时,可以直接使用,但是如果obj[key]是多层嵌套的引用类型呢?就使用如下情况:
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
let arrObj = {
name:'gxm',
age:18,
friends:['cc','yy','mm','nn',['cc','xx',['LL','QQ']]],
others:undefined,
address:null
}
let newArrObj = deepClone(arrObj);
newArrObj.age = 20;
newArrObj.friends[4][0] = 'gg';
console.log('原对象------',arrObj);
console.log('新对象------',newArrObj);
运行结果如下:
原对象------
{ name: "gxm",
age: 18,
friends: ['cc','yy','mm','nn',['cc','xx',['LL','QQ']]],
others: undefined,
address: null
}
新对象------
{
name: "gxm",
age: 20,
friends: ['cc','yy','mm','nn',['gg','xx',['LL','QQ']]],
others: undefined,
address: null
}
eg2: 如果拷贝的对象中的属性的值是循环引用的话,上面的深拷贝就不适用了,会崩掉的。要想解决这个问题,可以采用hash表进行映射。
function deepClone(obj,hash = new WeakMap()) {
//hash为了保存所有的数据,实际上是使用WeakMap将数据暂存下来,可以防止内存泄漏
//obj是不是对象并且不是null的情况
if(obj === null || typeof obj !== 'object'){
return obj;
}
//正则、时间
if(obj instanceof RegExp){
return new RegExp(obj);
}
if(obj instanceof Date){
return new Date(obj)
}
/*
* 第一次拷贝的时候hash表里肯定没有,下面的判断就不会执行。
* 之后如果hash表里有这个对象,就return出来,下面的for循环就不会执行。
* 即如果已经拷贝过了该属性,就不会再接着拷贝了,防止递归拷贝。
*/
if(hash.get(obj)){
return hash.get(obj);
}
//如果obj是的数组或对象时
let cloneObj = new obj.constructor();//获得obj的构造函数
//拷贝前和拷贝后进行对比
hash.set(obj , cloneObj);
for(let key in obj ){
if(obj.hasOwnProperty(key)){
cloneObj[key] = deepClone(obj[key], hash);
}
}
let symbolObj = Object.getOwnPropertySymbols(obj)
for (let i = 0; i < symbolObj.length; i++) {
if (obj.hasOwnProperty(symbolObj[i])) {
cloneObj[symbolObj[i]] = deepClone(obj[symbolObj[i]], hash)
}
}
return cloneObj;
}
let arrObj = {
name:'gxm',
age:18
}
arrObj.objj = arrObj;
let newArrObj = deepClone(arrObj);
console.log('原对象------',arrObj);
运行结果如下:
参考掘金小册->前端面试之道
如果有误,请留言!