简单实现

其实深拷贝可以分为两步,浅拷贝+递归,如果遇到对象就进行递归操作,两者相结合就实现了深拷贝。

1
function cloneShallow(source) {
2
    var target = Array.isArray(source) ? [] : {};
3
    for (var key in source) {
4
        if (Object.prototype.hasOwnProperty.call(source, key)) {
5
            if (isObject(source[key])) {
6
                target[key] = cloneDeep1(source[key]); // 注意这里
7
            } else {
8
                target[key] = source[key];
9
            }
10
        }
11
    }
12
    return target;
13
}
14
15
// 测试用例
16
var a = {
17
    name: "muyiy",
18
    book: {
19
        title: "You Don't Know JS",
20
        price: "45"
21
    },
22
    a1: undefined,
23
    a2: null,
24
    a3: 123
25
}
26
var b = cloneShallow(a);
27
28
a.name = "高级前端进阶";
29
a.book.price = "55";
30
31
console.log(b);
32
// { 
33
//   name: 'muyiy', 
34
//   book: { title: 'You Don\'t Know JS', price: '55' },
35
//   a1: undefined,
36
//   a2: null,
37
//   a3: 123
38
// }

处理循环引用

只需要使用一个hash来保存已经处理过的对象即可,这里使用ES6的WeakMap

1
function cloneDeep2(source,hash = new WeakMap()){
2
    if(!isObject(source)) return source;
3
    if(hash.has(source)) return hash.get(source)
4
5
    var target = Array.isArray(source) ? [] : {};
6
    hash.set(source, target); // 新增代码,哈希表设值
7
8
    for(var key in source) {
9
        if (Object.prototype.hasOwnProperty.call(source, key)) {
10
            if (isObject(source[key])) {
11
                target[key] = cloneDeep2(source[key], hash); // 新增代码,传入哈希表
12
            } else {
13
                target[key] = source[key];
14
            }
15
        }
16
    }
17
    return target;
18
}

在ES5中没有WeakMap,使用数组代替

1
function cloneDeep3(source, uniqueList=[]) {
2
3
    if (!isObject(source)) return source; 
4
5
    var target = Array.isArray(source) ? [] : {};
6
7
    // ============= 新增代码
8
    // 数据已经存在,返回保存的数据
9
    var uniqueData = find(uniqueList, source);
10
    if (uniqueData) {
11
        return uniqueData.target;
12
    };
13
14
    // 数据不存在,保存源数据,以及对应的引用
15
    uniqueList.push({
16
        source: source,
17
        target: target
18
    });
19
    // =============
20
21
    for(var key in source) {
22
        if (Object.prototype.hasOwnProperty.call(source, key)) {
23
            if (isObject(source[key])) {
24
                target[key] = cloneDeep3(source[key], uniqueList); // 新增代码,传入数组
25
            } else {
26
                target[key] = source[key];
27
            }
28
        }
29
    }
30
    return target;
31
}
32
33
// 新增方法,用于查找
34
function find(arr, item) {
35
    for(var i = 0; i < arr.length; i++) {
36
        if (arr[i].source === item) {
37
            return arr[i];
38
        }
39
    }
40
    return null;
41
}

处理递归暴栈

使用递归的方式处理深拷贝,如果对象过大可能会导致暴栈的问题,这里把递归变成循环,使用一个列表

1
function deepClone4(source)
2
{
3
    const hash = new WeakMap()
4
5
    var target = Array.isArray(source) ? [] : {};
6
    const loopList = [
7
        {
8
            parent: target,
9
            data: source,
10
            key: undefined
11
        }
12
    ]
13
14
    while (loopList.length) {
15
        const node = loopList.pop()
16
        const { parent, data, key } = node
17
18
        if (typeof data === 'object') {
19
            let child =  Array.isArray(data) ? [] : {};
20
            if (hash.get(data)) {
21
                child = hash.get(data)
22
                parent[key] = child
23
            } else {
24
                if (!key) {
25
                    child = parent
26
                } else {
27
                    parent[key] = child
28
                    hash.set(data, child)
29
                }
30
                for (let k in data) {
31
                    loopList.push({
32
                        parent: child,
33
                        key: k,
34
                        data: data[k]
35
                    })
36
                }
37
38
            }
39
        } else {
40
            parent[key] = data
41
        }
42
    }
43
44
    return target;
45
}