「面试重点」深浅拷贝以及手撕深拷贝

1,011 阅读2分钟

“Keep Learning!期待更好的自己”——大家好!我是Marine😄

小编荐语:本文旨在帮助大家彻底掌握深浅拷贝,以及手撕深拷贝

面试重点来啦!大家在面试时是不是经常被问到深浅拷贝相关的问题以及手撕一个深拷贝,如果是,那本文将是你的刚需😄。

一、数据类型

数据分为基本数据类型number、boolean、string、null、undefined、symbol、bigint和引用数据类型object

基本数据类型的特点:直接存储在栈(stack)中

引用数据类型的特点:存储在堆内存中,提供一个地址给栈内存调用

二、浅拷贝和深拷贝

深拷贝和浅拷贝只针对Object和Array这样的引用数据类型。

浅拷贝(浅克隆):只复制指向某个对象的指针,而不复制对象,新旧对象还是共享同一块内存。

深拷贝(深克隆):深拷贝会另外创造一个一模一样的对象,新对象跟愿对象不共享内存,修改新对象不会改到对象。

上面的概念太难理解了有没有,为了让大家理解更透彻,那我们来画图吧

let obj={
    a:100,
    b:[10,20,30],
    c:{
        x:10
    },
    d:/^\d+$/
}

浅拷贝:如图只是把第一级的地址进行克隆,克隆的地址改变,第二级的地址还是原来的地址,并没有改变所有的地址(b、c、d的地址没改变)

深拷贝:把第一级的地址和第二级的地址进行克隆,克隆的地址改变,改变了所有的地址(b、c、d的地址改变了)

三、浅拷贝的实现方式

  • Object.assign:实现对象的浅拷贝
let obj={
    a:100,
    b:[10,20,30],
    c:{
        x:10
    },
    d:/^\d+$/
}
//拷贝内存地址是不一样的
let newObj = Object.assign({},obj);
//验证是否浅拷贝(拷贝的第一级地址不同,拷贝的第二级地址相同)
console.log(newObj === obj)//=>false
console.log(newObj.b === obj.b)//=>true
  • {...obj}:展开运算符,也只能展开第一级,也是浅拷贝
let obj={
    a:100,
    b:[10,20,30],
    c:{
        x:10
    },
    d:/^\d+$/
}
//拷贝内存地址是不一样的
let newObj = (...obj);
//验证是否浅拷贝(拷贝的第一级地址不同,拷贝的第二级地址相同)
console.log(newObj === obj)//=>false
console.log(newObj.b === obj.b)//=>true
  • slice:实现数组的浅拷贝
let arr = [10,[100,200],{x:10,y:20}]
//拷贝内存地址是不一样的
let newArr = arr.slice();
//验证是否浅拷贝(拷贝的第一级地址不同,拷贝的第二级地址相同)
console.log(newArr === arr)//=>false
console.log(newArr[1] === arr[1])//=>true
console.log(newArr[2] === arr[2])//=>true
  • concat:实现数组的浅拷贝
let arr = [10,[100,200],{x:10,y:20}]
//拷贝内存地址是不一样的
let newArr = arr.concat([]);
//验证是否浅拷贝(拷贝的第一级地址不同,拷贝的第二级地址相同)
console.log(newArr === arr)//=>false
console.log(newArr[1] === arr[1])//=>true
console.log(newArr[2] === arr[2])//=>true

四、深拷贝的实现方式(暴力法)

暴力法原理:把原始数据直接改为字符串,再把字符串变为对象(此时浏览器要重新开辟所有的内存空间),实现出深度克隆

出现问题:正则会变为空对象/函数直接消失/日期直接变为字符串/Symbol直接消失/BigInt直接保错/undefined也会直接消失

let obj={
    a:100,
    b:[10,20,30],
    c:{
        x:10
    },
    d:/^\d+$/
}
//拷贝内存地址是不一样的
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj === obj)//=>false
console.log(newObj.b === obj.b)//=>false
console.log(newObj)

如上图所示正则直接变成了空对象

五、手撕深拷贝

现在除了JSON.parse(JSON.stringify())可以实现深拷贝,基本上js没有提供其他api给我们调用实现深拷贝了,但是JSON.parse(JSON.stringify())会造成一系列的问题,咋办呢?那我们来手写一个深拷贝吧!

let obj = {
	a: 100,
	b: [10, 20, 30],
	c: {
		x: 10
	},
	d: /^\d+$/
};
function cloneDeep(obj) {
	// 基于JSON.parse(JSON.stringify(obj))深拷贝会出现的问题进行类型判断
	if (obj === null) return null;
	//如果不是对象直接返回obj
	if (typeof obj !== "object") return obj;
	//针对正则和日期对象单独做处理
	if (/^(RegExp|Date)$/i.test(obj.constructor.name)) return new obj.constructor(obj);
	//克隆传进来的obj的数据格式
	let newObj = new obj.constructor();
	for (key in obj) {
		//只遍历obj的私有属性
		if (!obj.hasOwnProperty(key)) break;
		newObj[key] = cloneDeep(obj[key]);
	}
	return newObj;
}

这样就实现了深拷贝,正则也不会变成空对象

六、看完两件事❤

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我两个小忙:

1.点赞,转发,有你们的「在看」,才是我创造的动力。

2.同时可以期待后续文章ing🚀