JavaScript中的深拷贝和浅拷贝是前端面试过程中频繁被问到的一个问题,今天我就给大家简单讲解一下!
一、简单介绍
JavaScript中的变量一共有两种类型的值:基本类型值和引用类型值。
- 基本类型值
基本类型值,一般指String、Number、undefined、Null、Boolean等,他们在内存中都是存储在栈中的,直接访问该变量就可以得到存储在栈中的该变量的值。
若将一个变量的值赋值给另一个变量,则这两个变量在内存中都是独立的,修改其中一个变量的值,不会影响另一个变量。
下面我们来看一下图片介绍:
1.我们创建了第一个变量a,并赋值一个‘哈哈’这个值给它,则变量a在内存中的表现形式为:
2.然后我们又创建了一个变量b,接着把变量a的值赋值给变量b,则此时这两个变量在内存中的表现形式为:
可以看到在内存中的栈中,变量b独立占有了一个位置,并且还拥有了自己的值,为:‘哈哈’。
3.接着此时,我们把变量a的值改为‘嘿嘿’,那么此时这两个变量在内存中的栈中的情况是:
可以看到,只有变量a的值变为了‘嘿嘿’,这是因为变量b已经独立存在于内存中的栈中了,它不会受到变量a的影响。
- 引用类型值
引用类型值,一般指Object、Array、Function等,他们在内存中是存在于栈与堆中的,我们要访问到引用类型值的话,首先需要先访问到变量在栈中的堆地址(指向堆中的值),然后再通过栈中的堆地址访问到存放在堆中的数据。
1.我们先创建一个变量a,并赋值一个‘{name:‘张三’}’,此时在内存中的情况就是:
我们可以看到,给变量a赋值了一个Object数据类型后,在内存中是,Object存储在内存中的堆中,而内存中的栈中存储的是变量名和对应堆中的Object的地址。2.然后我们再创建一个变量b,接着将变量a赋值给变量b,此时内存中的情况是:
我们可以看到,当变量a把值赋值给了变量b的时候,只是在内存中的栈中创建了一个变量b,然后将变量a存储在栈中的堆地址1给了变量b,所以此时变量a和变量b都指向堆中的同一个值。
3.最后我们改变一下变量a中的值,a.name = '李四',此时内存中的情况是:
我们可以看出,因为变量a和变量b都指向堆中的同一个值,所以当通过变量a改变值时,变量a和变量b对应的值也都会跟着改变。
那么如何使变量b独立存在而不受变量a的影响呢?接下来我们看看浅拷贝和深拷贝。
二、浅拷贝
首先说一下,浅拷贝和深拷贝主要是针对像Objec和Array这两种比较复杂的对象的。
那么什么是浅拷贝呢?简单来说,就是一个变量赋值给另一个变量,其中一个变量的值改变,两个变量的值都跟着改变,这就叫做浅拷贝。
下面我们看一个简单的浅拷贝例子:
let a = { name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞'] }; let b = {}; for (let i in a) { b[i] = a[i]; }; a.name = '李四'; a.like[0] = '睡觉'; console.log('a:', a); //{name:'李四',age:19,like:['睡觉','唱歌','跳舞']} console.log('b:', b); //{name:'张三',age:19,like:['睡觉','唱歌','跳舞']}
我们可以看到,变量b通过一个一个获取变量a中的元素,已经独立存在,改变了变量a中的name,而变量b中的name却没有跟着改变。
但是,又能发现变量a改变了like值,变量b也随之改变了,好像并没有独立存在。
其实这是因为变量b在获取变量a的每一个元素时遇到了like,发现它是我们上边说到的引用类型,所以变量b就获得了一个地址,指向了堆中的值,所以变量b中的like仍然不是独立存在的。
这就是一个浅拷贝的例子,总结来说就是,浅拷贝只是将外层的值独立了出来,例如这个例子中的name、age,都不会随着变量a的改变而改变了;
但是由于对象结构复杂,内部的引用类型值没有独立出来,例如这个例子中的like,变量b获取到以后只是获取到了一个地址,它跟变量a的like指向同一个对象,所以变量b的like仍然会随着变量a的like改变而改变。
Object.assign
ES6语法中也给我们提供了一个浅拷贝的方法:Object.assign(target,sources)
- target:拷贝的目标
- sources:被拷贝的对象
let a = { name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞'] }; let b = {}; Object.assign(b,a); a.name = '李四'; a.like[0] = '睡觉'; console.log('a:',a);//{name:'李四',age:19,like:['睡觉','唱歌','跳舞']} console.log('b:',b);//{name:'张三',age:19,like:['睡觉','唱歌','跳舞']}
展开运算符...
let a = { name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞'] }; let b = {...a}; a.name = '李四'; a.like[0] = '睡觉'; console.log('a:', a); //{name:'李四',age:19,like:['睡觉','唱歌','跳舞']} console.log('b:', b); //{name:'张三',age:19,like:['睡觉','唱歌','跳舞']}
三、深拷贝
那么浅拷贝,是拷贝后,新拷贝的对象内部仍然有一部分数据会随着源对象的变化而变化;
那么深拷贝就是,拷贝后,新拷贝的对象内部所有数据都是独立存在的,不会随着源对象的改变而改变。
深拷贝的话,一共有两种方式:递归拷贝和利用JSON函数深拷贝
- 递归拷贝
实现原理:对变量中的每个元素进行获取,若遇到基本类型值,直接获取;若遇到引用类型值,则继续对该值的内部每个元素进行获取。
这里就不多做演示了,因为用递归来实现深拷贝很少,简单运用的话,用的最多的就是第二种深拷贝方式了。
但是还是会写个简单的递归拷贝例子进行参考。
let a = { name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞'] }; let b = {}; function deepCopy(Aobj, Bobj) { //遍历旧的对象(被拷贝的对象) for (let i in Aobj) { if (Aobj[i] instanceof Array) { //判断i的值是不是为数组,其实数组也是Object类型 Bobj[i] = []; deepCopy(Aobj[i], Bobj[i]); } else if (Aobj[i] instanceof Object) { //判断是不是对象 Bobj[i] = {}; deepCopy(Aobj[i], Bobj[i]); } else { //若是基本类型,直接赋值 Bobj[i] = Aobj[i]; } } }; deepCopy(a, b); a.name = '李四'; a.like[0] = '睡觉'; console.log('a:', a); //{name:'李四',age:19,like:['睡觉','唱歌','跳舞']} console.log('b:', b); //{name:'张三',age:19,like:['打篮球','唱歌','跳舞']}
- 利用JSON函数深拷贝
实现原理:将变量的值转变成字符串形式,然后再转化成对象赋值给新的变量。
let a = { name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞'] }; let b = JSON.parse(JSON.stringify(a)); a.name = '李四'; a.like[0] = '睡觉'; console.log('a:',a);//{name:'李四',age:19,like:['睡觉','唱歌','跳舞']} console.log('b:',b);//{name:'张三',age:19,like:['打篮球','唱歌','跳舞']}
可以看到,变量b已经完全独立存在了,无论变量a怎么变,变量b都保持不变了。