浅拷贝 and 深拷贝

A primitive (primitive value, primitive data type) is data that is not an object and has no methods. In JavaScript, there are 6 primitive data types: string, number, boolean, null, undefined, symbol (new in ECMAScript 2015).

All primitives are immutable (cannot be changed).

所以有六种原始类型:string, number, boolean, null, undefined, symbol。而这些原始类型不是一个对象,没有方法,不能改变。“不能改变”是指原始类型的value是immutable的,而variable是mutable的.所以说,对于原始类型的变量,为其赋值,本质上就是让变量指向新的内存.

而Object的赋值,则大不相同,涉及到深拷贝和浅拷贝。

  • 浅拷贝:浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响,浅拷贝只拷贝一层对象的属性;

  • 深拷贝:在堆中重新分配内存,并且把源对象所有属性都递归进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响。

需要注意的是,如果对象比较大,层级也比较多,深拷贝会带来性能上的问题。在遇到需要采用深拷贝的场景时,可以考虑有没有其他替代的方案。在实际的应用场景中,也是浅拷贝更为常用。

1. 浅拷贝

浅拷贝分两种情况,直接拷贝源对象的引用和源对象拷贝实例,但其属性(类型为Object,Array的属性)拷贝引用。

1.1 拷贝原对象的引用

var a = {c:1};
var b = a;
console.log(a === b); // true。
a.c = 2;
console.log(b.c); // 2

1.2 源对象拷贝实例,其属性对象拷贝引用。

这种情况,外层源对象是拷贝实例,如果其属性元素为复杂杂数据类型时,内层元素拷贝引用。
对源对象直接操作,不影响另外一个对象,但是对其属性操作时候,会改变另外一个对象的属性的值。
常用方法为:Array.prototype.slice(), Array.prototype.concat(), jQury的$.extend({},obj),例:

1.2.1 slice(0)

var a = [{c:1}, {d:2}];
var b = a.slice(0);
console.log(a === b); // false,说明外层数组拷贝的是实例
a[0].c = 3;
console.log(b[0].c); // 3,说明其元素拷贝的是引用

1.2.2 concat([])

var arr1 = [1,2,3];
var arr2=arr1.concat([]);
arr2[1] = 66;
console.log(arr1);  //[1, 2, 3]
console.log(arr2);  //[1, 66, 3]

1.2.3 $.extend({},obj)

var a={name:"mary",arr:[1,2,3],data:[{id:0},{id:1}]},
    g=$.extend({}, a);
    
    g.name='tom';
    g.arr[1]=22;
    g.data[1].id=33;

console.log(JSON.stringify(a));
//{"name":"mary","arr":[1,22,3],"data":[{"id":0},{"id":33}]}

console.log(JSON.stringify(g));
//{"name":"tom","arr":[1,22,3],"data":[{"id":0},{"id":33}]}

2. 深拷贝

深拷贝后,两个对象,包括其内部的元素互不干扰。常见方法有JSON.parse(),JSON.stringify(),jQury的$.extend(true,{},obj),lodash的_.cloneDeep和_.clone(value, true)。

2.1 JSON.parse(),JSON.stringify()

b = JSON.parse( JSON.stringify(a) );

局限性:

    无法复制函数;
    原型链没了,对象就是object,所属的类没了。
    

2.2 $.extend(true,{},obj)

var a={name:"mary",arr:[1,2,3],data:[{id:0},{id:1}]},
    g=$.extend(true,{}, a);
    
    g.name='tom';
    g.arr[1]=22;
    g.data[1].id=33;

console.log(JSON.stringify(a));
//{"name":"mary","arr":[1,2,3],"data":[{"id":0},{"id":1}]}

console.log(JSON.stringify(g));
//{"name":"tom","arr":[1,22,3],"data":[{"id":0},{"id":33}]}

2.3 _.cloneDeep

var a={name:"mary",arr:[1,2,3],data:[{id:0},{id:1}]},
    g=_.cloneDeep(a);
    
    g.name='tom';
    g.arr[1]=22;
    g.data[1].id=33;

console.log(JSON.stringify(a));
//{"name":"mary","arr":[1,2,3],"data":[{"id":0},{"id":1}]}

console.log(JSON.stringify(g));
//{"name":"tom","arr":[1,22,3],"data":[{"id":0},{"id":33}]}

2.4 自定义方案一:

var isType = function(type){
    return function(obj){
        //简单类型检测Object.prototype.toString
        return Object.prototype.toString.call(obj) === '[object '+ type +']';
    }
}
var is = {
    isArray : isType('Array'),
    isObject : isType('Object'),
}

var copyObj = function(result, source) {
    for (var key in source){
        var copy = source[key];

        if(is.isArray(copy)){
            //Array deep copy
            result[key] = copyObj(result[key] || [], copy);
        }else if(is.isObject(copy)){
            //Object deep copy
            result[key] = copyObj(result[key] || {}, copy);
        }else{
            result[key] = copy;
        }
    }
      return result;
}

var o1 = {
  number: 1,                                      
  string: "I am a string",                     
  object: {
    test1: "Old value"                             
  },
  arr: [                                        
    "a string",                                       
    {                                                  
      test2: "Try changing me"                
    }
  ]
};

var o2 = copyObj({},o1);
o2.object.test1 = "new Value";

console.log(JSON.stringify(o1));
//{"number":1,"string":"I am a string","object":{"test1":"Old value"},"arr":["a string",{"test2":"Try changing me"}]}

console.log(JSON.stringify(o2));
//{"number":1,"string":"I am a string","object":{"test1":"new Value"},"arr":["a string",{"test2":"Try changing me"}]}

o2.arr[1].subArr = [1,2,3];
console.log(JSON.stringify(o1));
//{"number":1,"string":"I am a string","object":{"test1":"Old value"},"arr":["a string",{"test2":"Try changing me"}]}

console.log(JSON.stringify(o2));
//{"number":1,"string":"I am a string","object":{"test1":"new Value"},"arr":["a string",{"test2":"Try changing me","subArr":[1,2,3]}]}

2.5 自定义方案二:

var cloneObj = function(obj){
    var str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
        return;
    } else if(window.JSON){
        str = JSON.stringify(obj), //系列化对象
        newobj = JSON.parse(str); //还原
    } else {
        for(var i in obj){
            newobj[i] = typeof obj[i] === 'object' ? 
            cloneObj(obj[i]) : obj[i]; 
        }
    }
    return newobj;
};

参考:

https://developer.mozilla.org/en-US/docs/Glossary/Primitive

http://yuanhehe.cn/2016/11/03/理解JS的浅拷贝与深拷贝/

https://yq.aliyun.com/articles/35053

posted @ 2018-01-05 09:55  MiyaMiya  阅读(282)  评论(0编辑  收藏  举报