在前面的章节中我们介绍过,JavaScript的数据类型分为7大原始数据类型和2中引用类型,原始数据类型存储在栈内存中,引用类型需要开辟单独的堆内存,变量存储堆内存的首地址,让我们来看一下这样一段代码,思考一些这个能实现数组的复制吗?

1
2
let arr = [1, 2, 3];
let cpArr = arr;

很明显,这个是不能够拷贝一份同样的arr数组的,第二行的意思是将数组arr的首地址赋值给cpArr,修改cpArr中的值,arr也会跟着修改。那么我们怎么才能拷贝一份引用类型的值呢?

这里介绍两种方向的拷贝:浅拷贝和深拷贝,浅拷贝只拷贝一级平面,深拷贝则递归拷贝所有层级,举个形象的例子,如下代码:

1
2
3
4
5
6
7
8
let obj = {
n: 'Que',
a: 22,
desc: {
hobby: 'game',
school: 'Michigan'
}
}

浅拷贝拷贝obj的n,a值,desc还是拷贝的内存地址;深拷贝会将所有值包括desc下面的几个属性也拷贝一份,修改不会产生映射。

浅拷贝

对象浅克隆

方法一: 循环遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let obj = {
n: 'Que',
a: 22,
desc: {
hobby: 'game',
school: 'Michigan'
}
}

let obj1 = {}

let keys = [
...Object.getOwnPropertySymbols(obj),
...Object.keys(obj)
]

keys.forEach(key => {
obj1[key] = obj[key]
})

/* 浅克隆结果就是这个,下面两种方法就不再写打印信息了 */
console.log(obj === obj1); // false
console.log(obj.desc === obj1.desc); // true

方法二: 基于展开运算符

1
let obj1 = { ...obj }

方法三: 基于Object.assign,将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。Object.assign(obj1,obj2) 返回结果依然是obj1堆内存,只不过是把obj2中的键值对和原始obj1键值对合并到一起了

1
let obj1 = Object.assign({}, obj)

数组浅克隆

克隆1:循环

1
2
3
4
5
6
7
let arr1 = [10, 20, [30, 40]];
let arr2 = [];
arr1.forEach((item, index) => { arr2[index] = item; })


console.log(arr2 === arr1) // -> false
console.log(arr2[2] === arr1[2]) // -> true

克隆2:基于map

1
arr2 = arr1.map(item => map);

克隆3: 基于展开运算符或者Object.assign

克隆4:基于slice

1
let arr2 = arr.slice();

深拷贝

当前级及其所有后代级别,都会克隆一份一摸一样的,和原始数据不存在任何关联;消耗内存空间

克隆1: JSON

  • 先基于JSON.stringify把原始对象或数组变为一个字符串
  • 再基于JSON.parse把字符串转为对象;此时对象对应每个级别的堆内存都是全新开辟的
  • 这种办法实现深克隆,正则对象会被处理为空对象,函数和Symbol也会被删掉,BigInt处理不了会报错,日期对象会变为字符串,undefined会被删除,总之遇到特殊数据类型,就会有各种问题
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
n: 'Que',
a: 22,
desc: {
hobby: 'game',
school: 'Michigan'
}
}

let obj1 = JSON.parse(JSON.stringify(obj));

console.log(obj === obj1); // false
console.log(obj.desc === obj1.desc); // false

克隆2: 递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let obj = {
n: 'Que',
a: 22,
desc: {
hobby: 'game',
school: 'Michigan'
}
}

function deepClone(sourceObj) {
let cloneObj = {}
for (let i in sourceObj) {
if (typeof sourceObj[i] === 'object') {
cloneObj[i] = deepClone(sourceObj[i])
} else {
cloneObj[i] = sourceObj[i]
}
}
return cloneObj
}

let obj1 = deepClone(obj)
console.log(obj === obj1); // false
console.log(obj.desc === obj1.desc); // false