一、js中的数据类型
在了解深拷贝与浅拷贝之前,我们先来了解一下js的数据类型,
在js中,数据类型可以分为如下两类:
- 基本数据类型(Number、boolean、String、NULL、undefined、Symbol(ES6))
- 引用数据类型
如上图所示,
1. 基本数据类型的数据名字和值都会保存在栈内存中
2. 引用数据类型,名字保存在栈内存中,值保存在堆内存中,而且栈内存中保存了指针,用于指向堆内存中的值。
由上我觉得可以简单把基本数据类型和引用数据类型理解为栈内存变量和堆内存变量,而所谓的深拷贝和浅拷贝,其实都是围绕堆栈内存来展开的,就是处理值与处理指针的问题。
二、直接赋值、浅拷贝与深拷贝的区别
之前一直以为赋值就是浅拷贝的一种,但其实不然,我们先来了解一下他们的含义。
- 赋值:将一个通过=号的方式直接赋值给另一个变量时,赋的是该对象在栈中的指向堆内存中值的指针,而不是在堆中的数据。
- 浅拷贝:浅拷贝会先创建一个新对象,再去遍历原对象,如果原对象的属性值是基本类型,那么拷贝的就是基本类型的值,如果属性值是引用类型,那么拷贝的就是指针。
- 深拷贝:深拷贝会拷贝所有的属性,以及它们的指针。
上图举例加深理解:
var a = [1, [1,2,3]]
var b=a;//直接赋值
var b= a.concat(); //浅拷贝
var b=JSON.parse(JSON.stringify(a)) //深拷贝
b[0]=2;
b[1].push(4)
console.table(a);
console.table(b);
粗略的原理图:
三、浅拷贝深拷贝的实现方式
一百度我们就会发现有几个同样的方法,有的人认为它是浅拷贝,而有的人则认为它是深拷贝。根据前两步以及它们的原理我们可以得知,同样的方法,面对不同的对象,它是可以既是深拷贝也是浅拷贝的。
1.既可是浅拷贝又可是深拷贝的方法:
1.1 Object.assign()
属性全为基本类型时为深拷贝
var obj = {
a: 1,
b: 2
}
var obj1 = Object.assign({},obj);
obj1.a = 3;
console.log(obj.a) // 1
属性中有引用类型时为浅拷贝
var obj = {
a: 1,
b:{c:1}
}
var obj1 = Object.assign({},obj);
obj1.b.c = 3;
console.log(obj.b.c) // 3
1.2 Array.prototype.concat()
let arr1=arr.concat();
1.3 Array.prototype.slice()
let arr1 = arr.slice();
1.4 扩展运算符(…)
let arr3 = [...arr1];
// 或 let obj1 = {...obj};
下面的方法跟第一种同理,不再赘述。
2.常用的深拷贝方法
2.1 JSON.parse(JSON.stringify(object))
不管是基本类型还是引用类型的属性发生改变都互不影响。
let a = {
name: "凌云00",
book: {
title: "快点加薪升职吧",
price: "12000"
}
}
let b = JSON.parse(JSON.stringify(a));
a.name = "凌云01";
a.book.price = "20000";
console.table(a);
console.table(b);
此方法的弊端:
- 会忽略undefine
- 会忽略symbol
- 不能序列化函数
- 会忽略空字符串
let obj = {
name: '凌云',
a: undefined,
b: Symbol('凌云'),
c: function() {},
d:null,
e:{},
f:""
}
let b = JSON.parse(JSON.stringify(obj));
console.table(b);
-
不能解决循环引用的对象(会报错)
-
不能正确处理new Date()
-
不能处理正则
2.2 jQuery.extend()
var array = [1,2,3,4];
var newArray = $.extend(true,[],array); // true为深拷贝,false为浅拷贝
2.3 lodash.cloneDeep()
let obj1 = _.cloneDeep(obj)
已经有人手动写得非常完善了,直接上链接
逐步深入手动实现深拷贝