# 13.深拷贝,浅拷贝

https://www.jianshu.com/p/1c142ec2ca45

# 浅拷贝

# 1. for···in 只循环第一层

// 只复制第一层的浅拷贝
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: {
        d: 3,
    },
};
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2. Object.assign 方法

var obj = {
    a: 1,
    b: 2,
};
var obj1 = Object.assign({}, obj);
obj1.a = 3;
console.log(obj.a); // 3
1
2
3
4
5
6
7

# 3. 直接用=赋值

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

# 实现深拷贝的方法

# 1. 递归实现(hasOwnProperty+typeof)

function deepClone(obj) {
    let objClone = Array.isArray(obj) ? [] : {};

    // 解决循环饮用问题
    if (map.get(obj)) {
        return map.get(obj);
    }
    map.set(obj, res);

    if (obj && typeof obj === "object") {
        for (key in obj) {
            // 使用for in 主要是兼顾考虑数组和对象的情况,如果从性能方面考虑,则数组:forEach,对象:Object.keys(target).forEach
            if (obj.hasOwnProperty(key)) {
                //判断ojb子元素是否为对象,如果是,递归复制
                if (obj[key] && typeof obj[key] === "object") {
                    objClone[key] = deepClone(obj[key]);
                } else {
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}
let a = [1, 2, 3, 4],
    b = deepClone(a);
a[0] = 2;
console.log(a, b);
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

# 2. 通过 JSON 对象来实现深拷贝

缺点:

  1. 如果 obj 里面有时间对象,则 JSON.stringify 后再 JSON.parse 的结果,时间将只是字符串的形式,而不是对象的形式
  2. 如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的结果将只得到空对象;
  3. 如果 obj 里有函数,undefined,则序列化的结果会把函数或 undefined 丢失;
  4. 如果 obj 里有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null
  5. 如果对象中存在循环引用的情况也无法正确实现深拷贝;
function deepClone2(obj) {
    var _obj = JSON.stringify(obj),
        objClone = JSON.parse(_obj);
    return objClone;
}
1
2
3
4
5

# 3. 最终版深拷贝

  1. 对于日期和正则的类型时, 进行处理 new 一个新的
  2. 对 a: { val: a } 这种循环引用时, 使用以 weakMap 进行巧妙处理
  3. 使用 Reflect.ownKeys 返回一个由目标对象自身的属性键组成的数组,
  4. 对于剩下的拷贝类型为 object 和 function 但不是 null 进行递归操作,
  5. 对于除了上述的类型外直接进行"key"的赋值操作。 细节处理:
  6. 利用 getOwnPropertyDescriptors 返回指定对象所有自身属性(非继承属性)的描述对象
  7. 将得到的属性利用 Object.create 进行继承原型链
  8. 对于 a: { val: a} 循环引用使用 weakMap.set 和 get 进行处理。 实现代码
  9. 用map代替weekmap也可以,主要是从性能考虑,使用weekmap

WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。因为 WeakMap 是弱引用类型,可以有效防止内存泄漏, 作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。 Reflect.ownKeys == Object.getOwnPropertyNames(target) contact (Object.getOwnPropertySymbols(target)。 Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。 Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组

const isComplexDataType = (obj) =>
    (typeof obj === "object" || typeof obj === "function") && obj !== null;
const deepClone = function(obj, hash = new WeakMap()) {
    if (obj.constructor === Date) return new Date(obj); // 日期对象直接返回一个新的日期对象
    if (obj.constructor === RegExp) return new RegExp(obj); //正则对象直接返回一个新的正则对象
    //如果循环引用了就用 weakMap 来解决
    if (hash.has(obj)) return hash.get(obj);
    let allDesc = Object.getOwnPropertyDescriptors(obj);
    //遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
    //继承原型链
    hash.set(obj, cloneObj);
    for (let key of Reflect.ownKeys(obj)) {
        // 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法
        cloneObj[key] =
            isComplexDataType(obj[key]) && typeof obj[key] !== "function" ?
            deepClone(obj[key], hash) :
            obj[key];
        //  typeof obj[key] !== 'function')
    }
    return cloneObj;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Last Updated: 6/3/2024, 1:08:34 AM