基本类型和引用类型的拷贝

JavaScript 的数据类型有两种:

  • 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol

  • 引用类型:Array、Object、Function

在对这两种类型进行拷贝时,表现有所不同

  • 对于基本数据类型,没有浅拷贝和深拷贝的区别

    1
    2
    3
    4
    5
    6
    7
    var a = 1;
    var b = a;

    console.log(b); // => 1

    a = 2;
    console.log(b); // => 1

    在对基本类型进行拷贝时,会在内存空间中新创建一块空间用于存放结果。尽管拷贝后结果相同,但ab指向的内存空间不同,因此对于任意变量的操作都不会影响另一个变量

  • 对于引用类型,区分深拷贝和浅拷贝

浅拷贝和深拷贝的区别

深拷贝和浅拷贝都是针对的引用类型

  • 对于浅拷贝,实际上拷贝的是内存地址,故修改任意一个对象的同时会影响另一个对象

  • 深拷贝可以将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

浅拷贝的实现

最简单的浅拷贝只需要进行赋值就可以,相当于为原对象添加了一个引用;这种情况下,原对象的每个改变都会影响复制后的对象

1
2
3
4
5
6
7
8
let arr = [1, 2, 3, { key: 'val' }];
let target = arr;
console.log(target); // => [ 1, 2, 3, { key: 'val' } ]

arr[1] = 'a';
arr[3].key = 'anotherVal';
console.log(target); // => [ 1, 'a', 3, { key: 'anotherVal' } ]
console.log(arr === target); // => true

可以对原对象进行更“深”一层的浅拷贝,也即是对象第一层的引用分离,而更深层的引用保持不变

1
2
3
4
5
6
7
8
9
10
11
12
13
function shallowCopy(source) {
if (typeof source === 'object') {
let target = (source instanceof Array) ? [] : {};
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
} else {
return source;
}
}
1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3, { key: 'val' }];
let target = shallowCopy(arr);
console.log(target); // => [ 1, 2, 3, { key: 'val' } ]
console.log(arr === target); // => false

arr[1] = 'a';
arr[3].key = 'anotherVal';
console.log(target); // => [ 1, 2, 3, { key: 'anotherVal' } ]
console.log(arr); // => [ 1, 'a', 3, { key: 'anotherVal' } ]

深拷贝的实现

递归

可以通过递归复制对象所有层级的属性

1
2
3
4
5
6
7
8
9
10
11
function deepCopy(source) {
if (typeof source === 'object') {
let target = (source instanceof Array) ? [] : {};
for (let key in source) {
target[key] = deepCopy(source[key]);
}
return target;
} else {
return source;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let person = {
name: {
first_name: 'Albert',
family_name: 'Einstein'
},
job: 'scientist'
}

let clonePerson = deepCopy(person);
person.name.first_name = 'Mike';
console.log(person);
// { name: { first_name: 'Mike', family_name: 'Einstein' }, job: 'scientist' }
console.log(clonePerson);
// { name: { first_name: 'Albert', family_name: 'Einstein' }, job: 'scientist' }

循环

使用递归存在一个问题,当被复制对象层级很深时会栈溢出,可以改用循环来实现深拷贝

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
32
33
34
35
36
37
38
39
40
41
42
function deepCopy(source) {
if (typeof source !== 'object') return source;

let target = (source instanceof Array) ? [] : {};
let loopList = [
{
parent: target,
key: undefined,
data: source
}
];

while (loopList.length) {
let node = loopList.pop(),
parent = node.parent,
key = node.key,
data = node.data;

// 初始化复制目标
// 对非引用类型,key 为 undefined,拷贝到父元素
// 对引用类型,拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
loopList.push({
parent: res,
key: k,
data: data[k]
});
} else {
res[k] = data[k];
}
}
}
}

return target;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let person = {
name: {
first_name: 'Albert',
family_name: 'Einstein'
},
job: 'scientist'
}

let clonePerson = deepCopy(person);
person.name.first_name = 'Mike';
console.log(person);
// { name: { first_name: 'Mike', family_name: 'Einstein' }, job: 'scientist' }
console.log(clonePerson);
// { name: { first_name: 'Albert', family_name: 'Einstein' }, job: 'scientist' }

使用 JSON 的 API

1
2
3
function deepCopy(source) {
return JSON.parse(JSON.stringify(source));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let person = {
name: {
first_name: 'Albert',
family_name: 'Einstein'
},
job: 'scientist'
}

let clonePerson = deepCopy(person);
person.name.first_name = 'Mike';
console.log(person);
// { name: { first_name: 'Mike', family_name: 'Einstein' }, job: 'scientist' }
console.log(clonePerson);
// { name: { first_name: 'Albert', family_name: 'Einstein' }, job: 'scientist' }