最近项目上js代码有些地方代码运用了深拷贝,前拷贝,但是二者的原理还不是很清楚,查阅了一些博客和技术文章,下面总结下,方便以后回忆。
在探究深拷贝与浅拷贝之前,我们先梳理一下JS的数据类型。在JavaScript中,数据类型有两大类。一类是基本数据类型,一类是引用数据类型。
基本数据类型有五种:number、string、boolean、null、undefined。基本数据类型存放在栈中。存放在栈中的数据具有数据大小确定,内存空间大小可以分配、直接按值存放的特点。所以存放在栈中的数据可以直接访问。
在JavaScript中,基本的数据类型值是不可更改的。可能这一点不符合我们平常的使用认知。我们在修改字符串或其他基本数据类型变量时,实际上是返回了一个新的值,而不是【改变】原始值。
而对于引用数据类型,是存放在堆内存中。引用数据类型的变量并不是存放的实际值,而是一个存放在栈内存的指针,该指针指向堆内存中的某个地址。每个数据所占的空间大小不一致,需要根据情况进行特定的分配。与基本数据类型不同,引用类型的值是可以改变的。
赋值:传值与传址
有了对JavaScript数据类型的一定了解后,我们接下来看看不同类型的数据类型在赋值时的区别。
对于基本数据类型来说,当我们进行赋值操作(=)时,实际上是在内存中新开一段栈内存,然后再将值赋值到新的栈中。
举个例子:
对于上述语句,b变量虽然是a变量的复制,但与a变量是独立互不影响的变量。
而对于引用数据类型来说,在进行赋值操作时,实际上是把变量的地址传给了另一个变量,所以称为传址。传址之后,两个变量就指向同一个地址,两者的操作是互有影响的。
浅拷贝与深拷贝
只有当拷贝引用数据类型时,拷贝才存在浅拷贝与深拷贝之分。
浅拷贝
浅拷贝就是指创建一个新对象,该对象拥有原始对象第一层属性的精确拷贝。即:如果原始对象的属性是基本类型数据,则拷贝的就是基本数据类型的值;如果原始对象的属性是引用类型,则拷贝的是内存地址。
当原始对象的引用类型属性发生改变时,拷贝对象的对应属性值也会发生变化。这里需要强调一下,浅拷贝与赋值是有所区别的,赋值时与原数据指向同一对象,而浅拷贝则指向了不同对象。
让我们来实现一个浅拷贝方法。
function shallowCopy(src) {
const dist = {}
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
dist[prop] = src[prop]
}
}
return dist
}
深拷贝
浅拷贝是对原始对象第一层属性的精确拷贝,而深拷贝则是对原始对象所有层级属性的递归精确拷贝。
深拷贝的实现
JSON
function deepCopy (src) {
let temp = JSON.stringify(src)
let dist = JSON.parse(temp)
return dist
}
递归实现
function deepCopy (src) {
if (typeof src !== 'object') {
return src
}
if (src == null) {
return null
}
let dist = Array.isArray(src) ? [] : {}
if (src && typeof src === "object") {
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
// 如果子属性为引用数据类型,递归复制
if (src[prop] && typeof src[prop] === "object") {
dist[prop] = deepCopy(src[prop])
} else {
// 如果是基本数据类型,只是简单的复制
dist[prop] = src[prop]
}
}
}
}
return dist
}
简单总结
- 浅拷贝只是拷贝一层,更深层次对象级别的
只拷贝引用(地址)
- 深拷贝拷贝多层,每一级别的数据都会拷贝
Object.assign(target,...sources)
es6新增方法可以浅拷贝
浅拷贝方法:Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
代码示例:
//通过js的内置对象JSON来进行数组对象的深拷贝
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
var objClone = JSON.parse(_obj);
return objClone;
}