深拷贝与浅拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,浅拷贝后的内容的内存地址相同
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
js
function shallowClone(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
在 JavaScript
中,存在浅拷贝的现象有:
方法 | 示例 | 适用场景 |
---|---|---|
Object.assign() | const copy = Object.assign({}, obj) | 简单对象合并,复制可枚举自有属性 |
展开运算符 ... | const copy = { ...obj } const arrCopy = [...arr] | 对象或数组的快速浅拷贝 |
Array.prototype.slice() | const arrCopy = arr.slice() | 数组浅拷贝 |
Array.prototype.concat() | const arrCopy = [].concat(arr) | 数组浅拷贝 |
手动遍历赋值 | javascript<br>const copy = {};<br>for (let key in obj) {<br> if (obj.hasOwnProperty(key)) {<br> copy[key] = obj[key];<br> }<br>} | 需要过滤原型链属性的场景 |
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
方法 | 示例 | 优缺点 |
---|---|---|
JSON.parse(JSON.stringify()) | const copy = JSON.parse(JSON.stringify(obj)) | 优点:简单 缺点:丢失函数、Symbol、undefined、循环引用报错 |
递归实现 | 见下方完整代码 | 优点:可定制处理特殊类型 缺点:需处理复杂边界 |
_.cloneDeep (Lodash) | const copy = _.cloneDeep(obj) | 优点:功能完善 缺点:需引入第三方库 |
structuredClone (现代 API) | const copy = structuredClone(obj) | 优点:原生支持,处理循环引用 缺点:不兼容 IE,无法克隆函数、DOM |
jQuery.extend() |
手写深拷贝
js
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
js
function deepClone(target, map = new WeakMap()) {
// 处理基本类型、函数(直接返回)
if (typeof target !== 'object' || target === null) return target;
// 处理循环引用:已拷贝过的对象直接返回
if (map.has(target)) return map.get(target);
// 处理特殊对象类型
const constructor = target.constructor;
let clone;
switch (constructor) {
case Date:
clone = new Date(target.getTime());
break;
case RegExp:
clone = new RegExp(target.source, target.flags);
break;
case Map:
clone = new Map();
target.forEach((v, k) => clone.set(deepClone(k, map), deepClone(v, map)));
break;
case Set:
clone = new Set();
target.forEach(v => clone.add(deepClone(v, map)));
break;
default:
// 继承原型链
clone = Object.create(Object.getPrototypeOf(target));
}
// 缓存当前对象,防止循环引用
map.set(target, clone);
// 处理普通对象和数组
Reflect.ownKeys(target).forEach(key => {
clone[key] = deepClone(target[key], map);
});
return clone;
}
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
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