浅拷贝与深拷贝什么关系?

前言

实现深浅克隆是面试当中出现频率非常高的题目,既然经常考,那么自然也用很多地方会用到它,比如说后台返回的一个对象,前端要对其加工,然后用来给不同的变量赋值,假如这个对象很复杂,就很容易被相互污染(因为对象是引用类型),那么这个时候就需要一个拷贝对象的函数替我们分忧~

浅拷贝

对于一个对象,只克隆其中最外面一层,深层的对象依旧是通过引用指向同一块堆内存。

ES5实现方式

我们可以看到,当tar对象为a对象的浅拷贝,只拷贝一层内容,再下一层还是用过引用指向同一块堆内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function shallowClone(sourceObj) {
// 验证是否为null
if (!sourceObj || typeof sourceObj !== "object") {
console.log("参数不为对象或数组");
return false;
}
// 判断sourceObj费类型,来相应设置变量
var taegetObj = sourceObj.constructor === Array ? [] : {};
for (var keys in sourceObj) {
// 用hasOwnProperty来过滤掉继承的对象
if (sourceObj.hasOwnProperty(keys)) {
taegetObj[keys] = sourceObj[keys];
}
}
return taegetObj;
}
var a = {
name: "huajinbo",
dream: {
eat: ["banana", "apple", "tomato"],
play: {
game: "Fruit ninja",
sport: "basketball"
}
}
};
let tar = shallowClone(a);
tar.dream.eat = ["pear"];
tar.name = 'vince'
console.log(tar.name === a.name); // false
console.log(tar.dream.eat === a.dream.eat); // true

ES6浅拷贝

这里我们用了ES6的结构赋值,快捷进行了浅拷贝,效果还是和上面的一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = {
name: "huajinbo",
dream: {
eat: ["banana", "apple", "tomato"],
play: {
game: "Fruit ninja",
sport: "basketball"
}
}
};
// targetObj = Object.assign(targetObj, a);
// 或者结构赋值
let tar = {...a}
tar.dream.eat = ["pear"];
tar.name = 'vince'
console.log(tar.name === a.name); // false
console.log(tar.dream.eat === a.dream.eat); // true

深拷贝

深拷贝意味着将整个对象完全拷贝,但是目前来说并没有完美的解决方案,但是对于很多情况来说,以下这种方案就能满足需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function deepClone (sourceObj) {
if(!sourceObj || typeof sourceObj !== 'object') {
console.log('非对象或数组')
return
}
return JSON.parse(JSON.stringify(sourceObj))
}
var a = {
name: "huajinbo",
dream: {
eat: ["banana", "apple", "tomato"],
play: {
game: "Fruit ninja",
sport: "basketball"
}
}
};

let tar = deepClone(a)
tar.dream.eat = ["pear"];
tar.name = 'vince'
console.log(tar.name === a.name); // false
console.log(tar.dream.eat === a.dream.eat); // false

虽然方法看上去简单粗暴,但是无论嵌套多深,这种方案总是能解决问题,似乎是个完美的解决方案。但是!当我们的对象中含有以下几类对象时:

  1. 对象中含有函数
  2. 对象中含有正则表达式
  3. 对象中含有稀疏数组
  4. 对象中含有构造函数

我们针对以上四个问题分别做测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 构造函数
function person(pname) {
this.name = pname;
}

const Messi = new person('Messi');

// 函数
function say() {
console.log('hi');
};

const oldObj = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Messi
};

const newObj = JSON.parse(JSON.stringify(oldObj));

// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]