JS基础知识总结:浅拷贝与深拷贝

本文将介绍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基础知识总结,主要介绍了深拷贝和浅拷贝的基本概念,并且分别又介绍了浅拷贝、深拷贝的实现方式,以及与赋值的区别。如有问题,欢迎指正。

你可能感兴趣的:(javascript,前端)