导读
深拷贝和浅拷贝是前端面试经常问的,可以考察面试者可以考察面试者的很多方面能力,而且可浅可深,本文是自己对深拷贝到理解以及如何实现深拷贝;
ECMAScript 的数据类型
在讲解深浅拷贝之前咱们先简单了解下ECMAScript 中的数据类型,数据主要分为基本数据类型和引用数据类型,下面分别讲述
基本数据类型
基本数据类型主要是:undefined,boolean,number,string,null,Symbol(es6新增)。
基本数据类型的特点
- 1.基本数据类型存放在栈中,存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
- 2.基本数据类型值不可变,原始值是不可更改的:任何方法都无法更改,对数字和布尔值来说, 改变数字的值本身就说不通,对于字符串老说,字符串的方法好像返回的是修改后字符串,实际上返回的是一个新的字符串值;

引用数据类型
引用数据类型的特点
- 1.引用类型(object)是存放在堆内存中的,存放引用数据类型的变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配。
- 2.引用类型是可以直接改变其值的,例如可以改变一个对象的某个属性的值,或者数组的某位的值;
- 3.引用类型的比较是引用的比较,工作中我们对数组或者对象的操作,本质上都是操作其对象的引用(保存在栈内存中的指针),所以比较两个引用类型,是看其的引用是否指向同一个对象

浅拷贝
浅拷贝对于基本数据类型拷贝的是变量的一个副本,修改原变量新变量不会随着修改;
//基本类型 let a = 1; let b = a; a = 2; console.log(a, b); // 2, 1 ,a b指向不同的数据
对于引用数据类型拷贝的是应用地址,原变量的改变会引起拷贝后的新变量的改变;
// 引用类型指向同一份数据 let obj = {name: 'js'}; let b = a; a.name = 'vue'; console.log(a.name, b.name); // vue, vue 全是vue,a b指向同一份数据
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象,其原理就是循环+递归
话不多说,下面我们直入正题,实现自己的深拷贝:
一行代码实现深拷贝
JSON.parse(JSON.stringify());
该方式不使用第三方库,写法非常简单,而且可以应对大部分的应用场景,但是有很大缺陷的,比如拷贝其他引用类型、拷贝函数、循环引用等情况;面试的时候只回答这个方法显然是不合格的;
深拷贝基础班
function deepClone(obj) {
if (obj == undefined) return obj
if (typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
let cloneObj = new obj.constructor;
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
cloneObj[k] = deepClone(obj[k])
}
}
return cloneObj
}
其原理就是递归循环,循环过程中对数据类型进行校验:
- 如果是原始类型,无需继续拷贝,直接返回。
- 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
- 当有循环引用的时候回出现死循环。
考虑数组
function deepClone(obj ) {
if (obj == undefined) return obj
if (typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
let cloneObj = new obj.constructor;
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
cloneObj[k] = deepClone(obj[k],)
}
}
return cloneObj
}
考虑循环引用
对于执行下面代码会因为循环应用出现死循环,所谓的死循环即对象的属性间接或直接的引用了自身的情况。对此我们的解决思路是,用键值对的形式将当前对象和需要拷贝的对象以键值对的形式储存起来,在循环拷贝之前先判断这个对象是否拷贝过了,如果拷贝过就不再进行拷贝,若拷贝过就进行拷贝;为此我们借助ES6的WeakMap实现。
const obj = {
name: 1,
age: 2,
hoby: ['basketball', 'games']
};
obj.obj = obj;
deepClone(obj) // Uncaught TypeError: Converting circular structure to JSON
// 真对循环引用我们的解决方案入下:
function deepClone(obj,map=new WeakMap ) {
if (obj == undefined) return obj
if (typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
let cloneObj = new obj.constructor;
let val =map.get(obj)
if(val){
return val
}else{
map.set(obj,cloneObj)
}
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
cloneObj[k] = deepClone(obj[k],map)
}
}
return cloneObj
}
小结
- 理解基本数据类型和引用数据类型以及二者的区别;
- 理解什么是浅拷什么是深拷贝;
- 理解深拷贝的实现思路,能手写一个比较完善的深拷贝;
文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞和关注。