JavaScript中的深拷贝和浅拷贝

什么是深拷贝/浅拷贝

  • 浅拷贝是创建一个新对象,这个对象具有原始对象属性值。如果属性是基本类型的话,拷贝的就是基本类型。如果属性是引用类型,拷贝的就是引用地址,改变了其中一个对象,就会影响到另外一个对象。
  • 深拷贝是将一个对象从内存中完整拷贝了一份,从堆内存中开辟了一个新的区域存放新对象,修改后不会影响原来的对象。

赋值、深拷贝、浅拷贝的区别

  • 对象数组赋值给一个新的变量时,其实是赋的内存地址,而不是对象中的数据。所以修改一个将影响另外一个。
1
var person1 = {
2
    name: '张三',
3
    age: 20,
4
    list: [1, 2, 3]
5
}
6
let arr = [4,5,6]
7
var person2 = person1;
8
person2.age = 22;
9
person2.list = arr;
10
person2.list[0] = '张三';
11
console.log(person1);  // {name: "张三", age: 22, list:["张三", 2, 3]}
12
console.log(person2);  // {name: "张三", age: 22, list:["张三", 2, 3]}
13
console.log(arr);      // ["张三", 5, 6]
  • 浅拷贝是重新在堆中创建内存,拷贝的基础类型互不影响,但是拷贝的引用类型共享同一个内存地址,会互相影响。

    1
    var person = {
    2
        name: '张三',
    3
        age: 20,
    4
        list: [1, 2, 3]
    5
    }
    6
    function shallowCopy(object) {
    7
        let temp = {};
    8
        for (const key in object) {
    9
            if (Object.hasOwnProperty.call(object, key)) {
    10
                temp[key] = object[key];
    11
            }
    12
        }
    13
        return temp;
    14
    }
    15
    var shallowPerson = shallowCopy(person)
    16
    console.log(shallowPerson);  // {name: "张三", age: 20, list: [1, 2, 3]}
    17
    shallowPerson.name = '李四';
    18
    shallowPerson.list[0] = 'hello';
    19
    console.log(person);         // {name: "张三", age: 20, list: ['hello', 2, 3]}
    20
    console.log(shallowPerson);  // {name: "李四", age: 20, list: ['hello', 2, 3]}
  • 深拷贝是从堆内存中从新创建一个区域来存放新对象,并对对象中的子对象进行递归拷贝,互不影响。

    1
    var person = {
    2
        name: '张三',
    3
        age: 20,
    4
        list: [1, 2, 3]
    5
    }
    6
    function deepCopy(object){
    7
        let temp = Array.isArray(object) ? [] : {};
    8
        for (let key in object) {
    9
            temp[key] = typeof object[key] === 'object' ? deepCopy(object[key]) : object[key]
    10
        }
    11
        return temp;
    12
    }
    13
    var shallowPerson = deepCopy(person)
    14
    console.log(shallowPerson);             // {name: "张三", age: 20, list: [1, 2, 3]}
    15
    shallowPerson.name = '李四';             
    16
    shallowPerson.list[0] = 'hello';        
    17
    console.log(person);                    // {name: "张三", age: 20, list: [1, 2, 3]}
    18
    console.log(shallowPerson);             // {name: "李四", age: 20, list: ['hello', 2, 3]}

实现的方式

浅拷贝的实现方式

ES6展开运算符 …

1
var person = {
2
    name: '张三',
3
    age: 20,
4
    list: [1, 2, 3],
5
    say:function(){console.log('hello');},
6
    check:new RegExp("e")
7
}
8
var user = { ...person}
9
user.name = '李四'
10
console.log(user.name);         // 李四
11
console.log(person.name);       // 张三

Object.assign()

1
var person = {
2
    name: '张三',
3
    age: 20,
4
    list: [1, 2, 3]
5
}
6
var user = Object.assign({},person)
7
console.log(user);  // // {name: "张三", age: 20, list: [1, 2, 3]}

lodash clone

1
var objects = [{ 'a': 1 }, { 'b': 2 }];
2
var shallow = _.clone(objects);
3
console.log(shallow[0] === objects[0]);
4
// => true

Array concat

Array 的 concat方法不改变原数组,返回一个浅复制原数组的元素的新数组。如果元素是引用类型,就会拷贝这个引用的内存地址到新数组内。两个元素引用地址共享,所以如果发生改变将影响另外一个。如果元素是基本类型的话就会遵循浅拷贝的机制

1
var array = [1,[2,3,4],[5,6],{name:'张三'}]
2
3
let array_concat = array.concat()
4
console.log(array_concat);  //  [1,2,3,[4,5,6],{name:'张三'}]
5
array_concat[1][0] = 'apple'
6
console.log(array_concat[1]);  // ["apple", 3, 4]      
7
console.log(array[1]);         // ["apple", 3, 4]
8
array_concat[2] = ['apple','mac','xiaomi']    // 这里之所以不一样,是因为改变了引用地址
9
console.log(array_concat[2]);  // ["apple", "mac", "xiaomi"]
10
console.log(array[2]);         // [5,6]
11
array_concat[3].name = '王五';
12
console.log(array_concat[3].name);  // 王五
13
console.log(array[3].name);         // 王五

遍历复制

1
function shallowCopy(object) {
2
    let temp = {};
3
    for (const key in object) {
4
        if (Object.hasOwnProperty.call(object, key)) {
5
            temp[key] = object[key];
6
        }
7
    }
8
    return temp;
9
}

深拷贝的实现方式

JSON.parse/JSON.stringify

可以满足基本的使用,但是对于正则表达式类型、函数类型等无法进行深拷贝。

1
var person = {
2
    name: '张三',
3
    age: 20,
4
    list: [1, 2, 3],
5
    say:function(){console.log('hello');},
6
    check:new RegExp("e")
7
}
8
var user = JSON.parse(JSON.stringify(person));
9
user.name = '李四';
10
console.log(user.name);             // 李四
11
console.log(person.name);           // 张三
12
user.list[0] = 'apple'
13
console.log(user.list);             // ["apple", 2, 3]
14
console.log(person.list);           // [1, 2, 3]
15
console.log(user.say);              // undefined
16
console.log(user.check);            // {}

lodash cloneDeep

1
var objects = [{ 'a': 1 }, { 'b': 2 }];
2
var deep = _.cloneDeep(objects);
3
console.log(deep[0] === objects[0]);
4
// => false

递归遍历

1
var person = {
2
    name: '张三',
3
    age: 20,
4
    list: [1, 2, 3],
5
    say: function () { console.log('hello'); },
6
    check: new RegExp("e")
7
}
8
9
10
function cloneDeep(object, hash = new WeakMap()) {
11
    if (!isObject(object)) return object;
12
    if(object instanceof Date) return new Date(object)
13
    if(object instanceof RegExp) return new RegExp(object)
14
    if (hash.has(object)) return hash.get(object)
15
    let target = Array.isArray(object) ? [] : {};
16
    hash.set(object, target);
17
    for (const key in object) {
18
        if (Object.hasOwnProperty.call(object, key)) {
19
            if (typeof object[key] === 'object') {
20
                target[key] = cloneDeep(object[key], hash)
21
            } else {
22
                target[key] = object[key];
23
            }
24
        }
25
    }
26
    return target;
27
}
28
29
function isObject(object) {
30
    return typeof object === 'object' && object != null;
31
}
32
33
var user = cloneDeep(person);
34
user.name = '李四';
35
console.log(user.name);             // 李四
36
console.log(person.name);           // 张三
37
user.list[0] = 'apple'            
38
console.log(user.list);             // ["apple", 2, 3]
39
console.log(person.list);           // [1, 2, 3]
40
console.log(user.say);              // undefined
41
console.log(user.check);            // {}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!