《深拷贝与浅拷贝的实现》
可先了解以下知识:
# 变量的存储
# 基本类型存储
- 变量名和值都直接存储在栈内存中
- 值与值之间独立存在,修改一个变量不会影响其他变量
var a = 20;
var b = a;
a++;
console.log(a, b) // 21 20
1
2
3
4
2
3
4
# 引用类型存储
- 变量名保存在栈中,对应的值是对象的内存地址(指针)
- 堆内存中才保存了真正的值
当改变一个变量的值时,另一个变量会跟着改变
var obj1 = new Object();
obj1.name = "mike";
var obj2 = obj1;
obj2.name = "jack";
console.log(obj1.name); //jack
1
2
3
4
5
2
3
4
5
清空一个变量的值时,只是断开该变量与对象的联系,另一个对象并不受影响
var obj1 = new Object();
obj1.name = "mike";
var obj2 = obj1;
obj2 = null;
console.log(obj1); // {name: "mike"}
console.log(obj2); // null
1
2
3
4
5
6
2
3
4
5
6
为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?
- 堆比栈大,栈比堆速度快;
- 基本数据类型比较稳定,而且相对来说占用的内存小;
- 引用数据类型大小是动态的,而且是无限的,引用值的大小会改变,如果存放在栈中,会降低变量查找的速度,因此放在栈空间的值是该对象存储在堆中的地址,通过地址再去堆中查找真正的值;
- 堆内存是无序存储,可以根据引用直接获取;
# 数据比较
- 当比较两个基本数据类型的值时,就是比较值;
- 当比较两个引用数据类型时,比较的是对象的内存地址;
var a = 10;
var b = 10;
console.log(a == b); // true => 值相等,为true
var obj1 = new Object();
var obj2 = new Object();
var obj3 = obj1;
console.log(obj1 == obj2);// false => 两个对象,引用地址不同
console.log(obj1 == obj3);// true => 引用地址相同
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 浅拷贝
从上面的介绍中可以知道,当改变一个引用类型变量的值时候,另一个变量值也会跟着改变。这是因为使用 =
赋值的时候,属于浅拷贝。
浅拷贝: 只拷贝对象的第一层属性,也就是只拷贝了其引用(指针),拷贝对象的改变会反应到原对象上。
# =
赋值
var obj1 = {
a: 1,
b: 2,
c: {
c1: 3,
c2: 4
}
};
var obj2 = obj1;
obj2.a = 100;
obj2.c.c1 = 300;
console.log(obj1.a, obj1.c.c1); // 100 300
console.log(obj2.a, obj2.c.c1);// 100 300
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# Object.assign()
Object.assign()
可以实现第一层基本类型属性的深拷贝,其他更深层属于浅拷贝。
var obj1 = {
a: 1,
b: 2,
c: {
c1: 3,
c2: 4
}
};
var obj2 = Object.assign({},obj1); // 需传{},否则第一层也属于浅拷贝
obj2.a = 100;
obj2.c.c1 = 300;
console.log(obj1.a, obj1.c.c1); // 1 300
console.log(obj2.a, obj2.c.c1); // 100 300
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 手动实现浅拷贝
只循环第一层,实现第一层的深拷贝
function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
obj2[i] = obj1[i];
}
return obj2;
}
var obj1 = {
a: 1,
b: 2,
c: {
c1: 3,
c2: 4
}
};
var obj2 = simpleCopy(obj1);
obj2.a[0] = 100;
obj2.c.c1 = 300;
console.log(obj1.a, obj1.c.c1); // 1 300
console.log(obj2.a, obj2.c.c1); // 100 300
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 深拷贝
深拷贝:拷贝多层,每一级别的数据都会拷贝出来,拷贝对象的改变不会影响到原对象。
# 使用 JSON 对象方法
缺点:
- 会抛弃对象的 constructor,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成 Object;
- 只有可以转成 JSON 格式的对象才可以这样用,像 function 就没办法转成 JSON;
var obj1 = {
a: 1,
b: ['a', 'b', 'c'],
c: {
c1: {
x: 1
}
}
};
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.c.c1.x = 300;
console.log(obj1.c.c1.x); // 1
console.log(obj2.c.c1.x); // 300
// function 无法转换
var obj3 = {
fun: function() {
alert(333)
}
}
var obj4 = JSON.parse(JSON.stringify(obj3))
console.log(typeof obj3.fun); // function
console.log(typeof obj4.fun); // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Object.assign()
- 当基本数据类型时,比如String,Number,Boolean时,属于深拷贝
- 当引用数据类型时,比如Object,Array时,属于浅拷贝
var obj1 = {
a: 1,
b: 2
};
var obj2 = Object.assign({},obj1); // 需要赋值给一个 {}
obj2.a = 100;
console.log(obj1.a) // 1
console.log(obj2.a) // 100
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 使用 Reflect
Reflect是ES6为操作对象而提供的新API。 Reflect介绍
function deepClone(obj) {
if (!isObject(obj)) {
throw new Error('obj 不是一个对象!')
}
let isArray = Array.isArray(obj)
let cloneObj = isArray ? [...obj] : { ...obj }
Reflect.ownKeys(cloneObj).forEach(key => {
cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return cloneObj
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# Object.create()
创建一个新对象
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用扩展运算符
- 当基本数据类型时,比如String,Number,Boolean时,属于深拷贝
- 当引用数据类型时,比如Object,Array时,属于浅拷贝
var car = {brand: "BMW", price: "380000", length: "5米"}
var car1 = { ...car, price: "500000" }
console.log(car1); // { brand: "BMW", price: "500000", length: "5米" }
console.log(car); // { brand: "BMW", price: "380000", length: "5米" }
1
2
3
4
5
2
3
4
5
# slice实现对数组的深拷贝
- 当基本数据类型时,比如String,Number,Boolean时,属于深拷贝
- 当引用数据类型时,比如Object,Array时,属于浅拷贝
var arr1 = ["1","2","3"];
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log("数组的原始值:" + arr1 );
console.log("数组的新值:" + arr2 );
1
2
3
4
5
2
3
4
5
# concat实现对数组的深拷贝
- 当基本数据类型时,比如String,Number,Boolean时,属于深拷贝
- 当引用数据类型时,比如Object,Array时,属于浅拷贝
// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝
var arr1 = ["1","2","3"];
var arr2 = arr1.concat();
arr2[1] = "9";
console.log("数组的原始值:" + arr1 );
console.log("数组的新值:" + arr2 );
// 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝
var arr1 = [{a:1},{b:2},{c:3}];
var arr2 = arr1.concat();
arr2[0].a = "9";
console.log("数组的原始值:" + arr1[0].a ); // 数组的原始值:9
console.log("数组的新值:" + arr2[0].a ); // 数组的新值:9
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# lodash.js
使用工具库 lodash
var _ = require('lodash');
var obj1 = {
a: 1,
b: ['a', 'b', 'c'],
c: {
c1: {
x: 1
}
}
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.c.c1.x === obj2.c.c1.x); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# $.extend
jquery 提供一个$.extend
方法实现深拷贝;
var $ = require('jquery');
var obj1 = {
a: 1,
b: ['a', 'b', 'c'],
c: {
c1: {
x: 1
}
}
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.c.c1.x === obj2.c.c1.x); // false
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 深度遍历优先拷贝
1
# 广度遍历优先拷贝
参考文章: