浅拷贝与深拷贝

浅拷贝与深拷贝

1 前言

读完全文,希望你能明白:

  • 什么是深/浅拷贝?
  • 深/浅拷贝的实现方式有几种?

2 基本类型 & 引用类型

ECMAScript 中的数据类型可分为两种:

  • 基本类型:undefined、null、Boolean、String、Number、Symbol
  • 引用类型:对象类型 Object type,Object、Array、Date、Function、RegExp等

不同的复制方式:

  • 基本类型:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
1
2
3
4
5
6
7
8
let foo = 1;
let bar = foo;
console.log(foo === bar); // -> true

// 修改foo变量的值并不会影响bar变量的值
let foo = 233;
console.log(foo); // -> 233
console.log(bar); // -> 1
  • 引用类型:从一个变量向另一个新变量复制引用类型的值,其实复制的是指针(内存地址),最终两个变量最终都指向同一个对象。
1
2
3
4
5
6
7
8
9
10
11
let foo = {
name: 'leeper',
age: 20
}
let bar = foo;
console.log(foo === bar); // -> true

// 改变foo变量的值会影响bar变量的值
foo.age = 19;
console.log(foo); // -> {name: 'leeper', age: 19}
console.log(bar); // -> {name: 'leeper', age: 19}

3 浅拷贝 & 深拷贝

注:深浅拷贝这个说法是针对引用类型

  • 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
  • 深拷贝:不同的地址,相同的值,互不影响

总的来说,深浅拷贝的主要区别就是:复制的是引用还是复制的是实例

借助 ConardLi大佬 以下两张图片,帮我们更好的理解两者的含义:

image.png

image.png

3.1 浅拷贝

3.1.1 数组

  • Array.prototype.slice()
1
2
3
4
5
6
7
8
let a = [1, 2, 3, 4];
let b = a.slice();
console.log(a === b); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]

  • Array.prototype.concat()
1
2
3
4
5
6
7
let a = [1, 2, 3, 4];
let b = a.concat();
console.log(a === b); // -> false

a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
  • ES6扩展运算符
1
2
3
4
5
var arr = [1,2,3,4,5]
var [ ...arr2 ] = arr
arr2[2] = 5
console.log(arr); // [ 1, 2, 3, 4, 5 ]
console.log(arr2); // [ 1, 2, 5, 4, 5 ]

3.1.2 对象

  • Object.assign({}, obj);
1
2
3
4
5
var a = {'name': 'xiaoyu'};
var b = Object.assign({}, a);
b.name = 'xiaoyu2';
console.log(a.name);//xiaoyu
console.log(b.name);//xiaoyu2
  • 扩展运算符
1
2
3
4
5
var a = {'name': 'xiaoyu'};
var { ...b } = a;
b.name = 'xiaoyu2';
console.log(a.name); // xiaoyu
console.log(b.name); // xiaoyu2
  • 3.1.3 函数库lodash的_.clone方法
1
2
3
4
5
var _ = require('lodash');
var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]); // true

以上看起来似乎是深拷贝,再接着看就知道它们究竟是深拷贝还是浅拷贝:

1
2
3
4
5
6
7
let a = [[1, 2], 3, 4];
let b = a.slice();
console.log(a === b); // -> false

a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
1
2
3
4
5
var a = {'name': { 'zh': 'yushuang', 'en': 'shuangyu' } };
var b = Object.assign({}, a);
b.name.zh = 'yushuang2';
console.log(a.name); // { zh: 'yushuang2', en: 'shuangyu' }
console.log(b.name); // { zh: 'yushuang2', en: 'shuangyu' }

综上, 这些方法都并不是真正的深拷贝,对于第一层的元素是深拷贝,而第二层是复制引用。

3.2 深拷贝

  • JSON.parse()和JSON.stringify()
  1. JSON.stringify():把一个js对象序列化为一个JSON字符串
  2. JSON.parse():把JSON字符串反序列化为一个js对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
name: 'leeper',
age: 20,
friend: {
name: 'lee',
age: 19
}
};
let copyObj = JSON.parse(JSON.stringify(obj));
obj.name = 'Sandman';
obj.friend.name = 'Jerry';
console.log(obj);
// -> {name: "Sandman", age: 20, friend: {age: 19,name: 'Jerry'}}
console.log(copyObj);
// -> {name: "leeper", age: 20, friend: {age: 19,name: 'lee'}}

综上,JSON.parse() 和 JSON.stringify() 是完全的深拷贝。但是,这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于 JSON.stringify 和 JSON.parse 处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了,举个例子如下:

1
2
3
4
5
6
let arr = [1, 3, {
username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)

image.png

  • 函数库lodash的_.cloneDeep方法
1
2
3
4
5
6
7
8
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
  • jQuery.extend()
1
$.extend(deepCopy, target, object1, [objectN]); //第一个参数为true,就是深拷贝
1
2
3
4
5
6
7
8
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
  • 手动实现一个深拷贝
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
function clone(target, map = new WeakMap()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return target;
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
}

const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8],
f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } } },
};

target.target = target;
const result = clone1(target);
console.log(result);
console.log(target);
作者

喻双

发布于

2021-08-04

更新于

2022-08-01

许可协议

CC BY-NC-SA 4.0

评论