本文将介绍JS的深拷贝和浅拷贝的有关内容。

1.基本概念

(1)深拷贝和浅拷贝针对的是引用类型。基本类型的名值存储在栈中,当复制时,栈内存会开辟一个栈内存。所以二者修改时,彼此不会影响。

(2)浅拷贝复制的是指向对象的指针,并没有开辟新的栈内存,原对象和新对象还是共享同一块内存,修改新对象自然会影响原对象。

深拷贝会开辟新的栈内存,原对象和新对象不共享同一块内存,修改新对象不会影响到原对象。

2.实现方式

2.1浅拷贝的实现方式

2.1.1Object.assign()

可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

Object.assign()只会拷贝所有的属性值到新的对象中,如果属性值是基本类型,则修改其中一个对象,不会影响另一个。而如果属性值是对象的话,拷贝的是对象的引用,而不是对象本身。所以该方法是浅拷贝。

分两种情况:

(1)属性值是基本类型,修改其中一个对象,不会影响另一个对象的该属性:

var iniObj = {
    name: "peter",
 };
 var newObj = Object.assign({}, iniObj);
 newObj.name = "lily";
 console.log('iniObj.name', iniObj.name); //peter

(2)属性值是引用类型,由于拷贝的是对象的引用,共享相同的地址:

var iniObj = {
    info: {
        name: "peter",
        age: 8
    }
};
var newObj = Object.assign({}, iniObj);
newObj.info.name = "lily";
console.log('iniObj.info.name', iniObj.info.name); //lily

2.1.2Array.prototype.concat()

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。注意这里分两种情况:

(1)对象引用(而不是实际对象):concat将对象引用复制到新数组中。原始数组和新数组都引用相同的对象。也就是说,如果引用的对象被修改,则更改对于新数组和原始数组都是可见的。这包括也是数组的数组参数的元素。

(2)数据类型如字符串,数字和布尔(不是StringNumberBoolean 对象):concat将字符串和数字的值复制到新数组中

示例:

var iniArr = [1,2,{name:"peter"}];
var newArr = iniArr.concat();
newArr[1] = 8;
newArr[2].name = "lily";
console.log('iniArr',iniArr);

结果:

2.1.3Array.prototype.slice()

slice()方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。

也分两种情况:

(1)如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

(2)对于字符串、数字及布尔值来说(不是 StringNumber 或者 Boolean 对象),slice 会拷贝些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

示例:

var iniArr = [1,2,{name:"peter"}];
var newArr = iniArr.slice();
newArr[1] = 8;
newArr[2].name = "lily";
console.log('iniArr',iniArr);

结果:

和上述concat()一样,修改新数组的对象会影响到原数组,修改新数组的基本类型值不会影响到原数组:

2.1.4引用复制

示例:

            var iniObj = {
                gender: "male",
                info: {
                    name: "peter",
                    age: 8
                },
                test: [1, [2, 3]]
            };

            function shallowCopy(copyObj) {
                var obj = {};
                for (var i in copyObj) {
                    obj[i] = copyObj[i];
                }
                return obj;
            }

            var newObj = shallowCopy(iniObj);
            newObj.gender = "female";
            newObj.info.name = "lily";
            newObj.test[1] = [4, 5];
            console.log('iniObj', iniObj);
            console.log('newObj', newObj)    

结果:

2.2深拷贝的实现方式

2.2.1JSON.parse(JSON.stringify())

说明:

1)用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象。一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

2)这种方法虽然可以实现数组或对象的深拷贝,但不能处理函数。因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数

示例:

var iniArr = [1,2,{name:"peter"},function(){}];
var newArr = JSON.parse(JSON.stringify(iniArr));
newArr[1] = 8;
newArr[2].name = "lily";
console.log('JSON.parse(Json.stringfiy()):iniArr,newArr',iniArr,newArr);

结果:

2.2.2递归方法

原理:

遍历对象、数组,直到里边都是基本数据类型,然后再去复制,即可实现深度拷贝。

代码:

            var iniObj = {
                a: 1,
                b: [1, 2, 3],
                c: {
                    d: {
                        e: 4
                    }
                },
                fun: function(){
                    return 999;
                }
            };

            function deepClone(x) {
                if (x.constructor === Object) {
                    var obj = {}
                    for (var k in x) {
                        obj[k] = deepClone(x[k])
                    }
                    return obj
                } else if (x.constructor === Array) {
                    var arr = [];
                    for (var i = 0; i < x.length; i++) {
                        arr[i] = deepClone(x[i])
                    }
                    return arr
                } else {
                    return x
                }
            }
            var newObj = deepClone(iniObj)

            newObj.b[0] = 6;
            newObj.c.d.e = 8;
            console.log('recursive deepClone,iniObj,newObj',iniObj,newObj);

结果:

该方法的局限性:

若属性值是函数、undefined、symbol时,会忽略掉:

示例:

            var iniObj = {
                name:'peter',
                age:undefined,
                school:function(){}
            }
            var newObj = JSON.parse(JSON.stringify(iniObj));
            console.log('newObj',newObj)     

结果:

尽管有上述局限,不过通常情况下,该函数还是可以解决大部分问题的。

2.2.3jQuery的$.extend()

可以用jQuery的$.extend()实现深拷贝:

jQuery.extend([deep], target, object1, [objectN]);

示例:

var iniObj = {
    a: 1,
    b: [1, 2, 3],
    c: {
           d: {
                  e: 4
           }
    },
};
var newObj = $.extend(true, {}, iniObj);
newObj.b[0] = 6;
newObj.c.d.e = 8;
console.log('$.extend deepClone,iniObj,newObj',iniObj,newObj);

结果:

2.2.4lodash的_.cloneDeep()

可以用js库lodash中的_.cloneDeep()实现深拷贝:

示例:

            var iniObj = {
                a: 1,
                b: [1, 2, 3],
                c: {
                    d: {
                        e: 4
                    }
                }
            };
            var newObj = _.cloneDeep(iniObj);
            newObj.b[0] = 6;
            newObj.c.d.e = 8;
            console.log('lodash deepClone,iniObj,newObj',iniObj,newObj);

结果:

3.赋值

可以和上述2.1.4浅拷贝的示例结果做比较,示例:

            var iniObj = {
                gender: "male",
                info: {
                    name: "peter",
                    age: 8
                },
                test: [1, [2, 3]]
            };

            var newObj = iniObj;
            newObj.gender = "female";
            newObj.info.name = "lily";
            newObj.test[1] = [4, 5];
            console.log('iniObj', iniObj);
            console.log('newObj', newObj)        

结果:

说明把一个对象赋值给另一个变量时,并没有创建一个新对象,而是把原对象在栈中的地址(而非栈中的数据)赋给了新对象,即赋的是原对象在栈中的地址,原对象和新对象指向的是同一个地址。因此,两个对象的联动的,修改其中一个,另一个也会改变。包括里面所有的属性,不论是基本类型的数据,还是对象引用。

4.小结

本篇JS基础知识总结,主要介绍了深拷贝和浅拷贝的基本概念,并且分别又介绍了浅拷贝、深拷贝的实现方式,以及与赋值的区别。如有问题,欢迎指正。


陌然浅笑
25 声望3 粉丝

坚持自律