前言
作为前端开发人员,当我们在进行项目开发统筹的过程中,对于从后端传输过来的JavaScript数据,我们经常会对此进行对象拷贝,数组拷贝等数据操作,因为这有助于之后的数据对比及恢复数据。像这样类似于复制JS数据的这种操作行为在JS中统称为深浅拷贝。近期的春招面试开始了,想必大量的面试官会考察这个基本的知识点,你对这个知识点是否有清晰的了解呢?或许我们经常进行数据拷贝,但是可能忽略了背后的原理,深拷贝和浅拷贝的区别又是什么呢?让我们一起来一探究竟吧!🚗
数据类型
一般来说,遇到深浅拷贝的问题,本质上都是针对引用数据类型的变量操作。如果要深入了解这个话题,就不得不对JS的数据类型有所了解。大家都知道JavaScript中的数据通常分为两种,基本数据类型和引用数据类型
其中:
- 基本数据类型: Number、String、Null、Undefined、Boolean、Symbol
- 引用数据类型: obj,array,function...
什么是引用数据类型,什么又是基本数据类型呢?这就要普及一下
JS的内存机制了😀
在JS中,每一个数据都需要一个内存空间。内存空间又被分为两种,栈(stack) 和 堆(heap)
- 栈:由系统自动分配,自动回收,效率高,但容量小。
- 堆:由程序员手动分配内存,并且手动销毁,效率不如栈,但容量大。
其实很简单,基本类型就是保存在栈中的简单数据段,而引用类型指的是保存在堆中的对象。JS中的基本数据类型一般是存储栈中,这是由于这些基本类型在内存中占有大小固定的空间,即值的大小一定,所以这些具体的值往往保存在栈空间(闭包除外),我们可以直接操作保存在栈的值,如字面意思,这种访问方式为按值访问。
而引用类型的值大小不固定,但是,他们的地址大小是固定的,所以,为了存储这个类型,系统将能够访问这个类型的地址存储在栈中,并给这个变量的本身进行赋值,并将其具体的内容存入堆中。引用数据的访问方式类似于指针,通过栈中访问对象的地址,并按照这个地址在堆中找到对象具体的内容。
看到这里你是否会有些明白呢?之所以会有深浅拷贝的概念,是由于JS对基本类型和引用类型的处理方式不同。我们可以直接在栈内存中访问数据类型,即直接操作内存空间,按值访问。但是对于引用数据,JS不允许我们像前者那样直接访问,看似在操作对象,其实我们只是在操作对象的引用而已。复制也是同样如此,如果我们要复制一个基本的数据类型的值,由于是简单的数据,我们直接在栈内存中开辟相同大小的空间,并将其保存在新的变量上去,这样,值与值之间是独立存在,且修改一个变量不会影响其他的变量。但是对于引用类型的复制,我们复制的值就不再是引用内容的值,而是指向该对象的指针。两个指针指向相同的内存空间,如果操作复制出来的对象,就会对源对象和源数据产生影响。
var obj1={
name:'莹莹'
}
var obj2=obj1;
obj2.name='茵茵';
console.log('obj1.name',obj1.name); // '茵茵'
console.log('obj2.name',obj2.name); // '茵茵'
😉以上为一个简单的栗子。
浅拷贝
什么是浅拷贝?
讲完了数据类型的区别,终于可以来介绍拷贝方式了,浅拷贝其实就是拷贝指向对象的指针,在百度百科中,有这样的介绍:
拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间,浅拷贝只是一种简单的拷贝,让几个对象公用一个内存,然而当内存销毁的时候,指向这个内存空间的所有指针需要重新定义,不然会造成野指针错误。
可见,浅拷贝只是复制基本类型的数据或者指向某个对象的指针,而不是复制整个对象,如果修改目标对象,那么,源对象会有被修改的可能
浅拷贝的实现方式
以下是实现浅拷贝的几种常见方法
- Object.assign()
var obj = { a: {a: "www", b: 39} };
var initalObj =
Object.assign({}, obj);
initalObj.a.a = "yyy";
console.log(obj.a.a);
//yyy
注意: 有一个特殊情况,当object只有单层属性的时候,是深拷贝
let obj = {
username: 'www'
};
let obj2 = Object.assign({},obj);
obj2.username = 'yyy';
console.log(obj); //{username: "www"}
- Array.prototype.concat()
let arr = [1, 3, { username: 'www' }];
let arr2=arr.concat();
arr2[2].username = 'yyy';
console.log(arr);
- Array.prototype.slice()
let arr = [1, 3, { username: ' www' }];
let arr3 = arr.slice();
arr3[2].username = 'yyy'
console.log(arr);
深拷贝
什么是深拷贝?
如果说浅拷贝无法满足完美主义开发者的需求,那么,其实,深拷贝才能实现真正意义上的拷贝!和浅拷贝只复制对象指针不同,深拷贝会另外创造一个一模一样的对象,新对象和源对象不会互相干扰,修改新对象不会影响源对象。
深拷贝的实现方式
目前深拷贝的实现方法主要有递归复制和 JSON正反序列化
- JSON.parse() 和 JSON.stringify()
let obj = {
name: 'yyy',
age: 20,
friend: {
name: 'lll',
age: 19
}
};
let copyObj = JSON.parse(JSON.stringify(obj));
obj.name = 'aaa';
obj.friend.name = 'bbb';
console.log(obj);
console.log(copyObj);
- 递归 对属性所有引用值进行遍历,直到是基本类型的值为止
function deepCopy(obj) {
if (!obj && typeof obj !== 'object') {
throw new Error('error arguments');
}
// const targetObj = obj.constructor === Array ? [] : {};
const targetObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
//只对对象自有属性进行拷贝
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
targetObj[key] = deepCopy(obj[key]);
} else {
targetObj[key] = obj[key];
}
}
}
return targetObj;
}
参考文章
总结
虽然拷贝在JS中是一个挺常用的操作,但是还是需要常常复习的,如果文章有不对的地方也欢迎在评论中指出,谢谢您的阅读!