javascript中的深浅拷贝(非构造函数的继承)

首先,我们应该明确深浅拷贝是针对ArrayObject这样的复杂类型的。

一 常见的深浅拷贝的方法

(1)数组和对象的浅拷贝
如果是数组,我们可以利用数组的一些方法比如:sliceconcat 返回一个新数组的特性来实现拷贝。

//新旧数组都改变,对象指向同一块地址
var arr=['old',{"old":"old"},["old"],null,undefined];
var newArr=[];

//newArr=arr.concat();
newArr=arr.slice();

newArr[0]='new';
newArr[1].old="new";
newArr[2][0]="new";

console.log(arr);//[ 'old', { old: 'new' }, [ 'new' ], null, undefined ]
console.log(newArr);//[ 'new', { old: 'new' }, [ 'new' ], null, undefined ]

可以看到,当我们使用silce或者concat方法生成一个新数组时,如果我们改变值类型,那么改变互不影响;如果改变的是数组、对象这样的引用类型,那么无论新旧数组发生改变,两者都会变化(实际上我们拷贝的是地址或者说是对象或数组的引用)

Object.assign():ES6中定义的,将源对象(source)的所有可枚举属性复制到目标对象(target)。

Object.assign(target,...source);
var obj1={old1:"old1"};
var target=Object.assign(obj1);
target.old1="target1";
console.log(target.old1);//target1
console.log(obj1.old1);//target1
var obj=[{old:"old"}];
var target=Object.assign(obj);
target[0].old="new";
console.log(target);//[ { old: 'new' } ]
console.log(obj);//[ { old: 'new' } ]

(2)数组和对象的深拷贝
JSON.parse:可以将JSON字符串反序列化成JS对象
JSON.stringify:可以将JS对象序列化成JSON字符串

var arr=['old',{"old":"old"},["old"],null,undefined];
var newArr=[];

newArr=JSON.parse(JSON.stringify(arr));

newArr[0]='new';
newArr[1].old="new";
newArr[2][0]="new";

console.log(arr);//[ 'old', { old: 'old' }, [ 'old' ], null, undefined ]
console.log(newArr);//[ 'new', { old: 'new' }, [ 'new' ], null, null ]

我们发现,当使用JSON.parseJSON.stringify进行拷贝时,拷贝前后的两个数组是互不相干的,对拷贝产生的数组和源数组的各自操作互不影响。

这种方法的缺点:

  • 能正确处理的对象只有NumberStringBooleanArray扁平对象等能够被json表示的数据结构,因此函数这种不能被json表示的类型,如fucntionRegExp类型将不能被正确处理。
var arr=[1,"old",[1,2,3],function(){return "old";},undefined,new RegExp("old")];
var newArr=[];

newArr=JSON.parse(JSON.stringify(arr));

console.log(arr);//[ 1, 'old', [ 1, 2, 3 ], [Function], undefined, /old/ ]
console.log(newArr);//[ 1, 'old', [ 1, 2, 3 ], null, null, {} ]
  • 拷贝后的对象会丢弃对象的constructor,也就是拷贝后,无论这个对象原来的构造函数是什么,在深拷贝之后都会变成object
  • 如果对象中存在循环引用的情况,也无法正确处理。

二 深浅拷贝的区别
JavaScript存储对象都是存地址的。
浅拷贝:会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,
深拷贝:开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。

三 自己实现深浅拷贝
浅拷贝:

function shallowCopy(obj){
    if(typeof obj !== "object") return;//只针对object
    var newObj=obj instanceof Array?[]:{};//根据obj的类型新建一个数组或者对象
    for(var i in obj){//遍历obj的所有可枚举属性,包括实例上的和原型上的
        if(obj.hasOwnProperty(i)){//只拷贝实例上的属性
            newObj[i]=obj[i];
        }
    }
    return newObj;
}
var arr=[1,"old",[1,2,3],function(){return "old";},undefined,new RegExp("old")];
var newArr=shallowCopy(arr);
arr[2][2]=4;
newArr[4]="new";
console.log(arr);//[ 1, 'old', [ 1, 2, 4 ], [Function], undefined, /old/ ]
console.log(newArr);//[ 1, 'old', [ 1, 2, 4 ], [Function], 'new', /old/ ]

深拷贝:

function deepCopy(obj){
    if(typeof obj !== "object") return;//只针对object
    var newObj=obj instanceof Array?[]:{};//根据obj的类型新建一个数组或者对象
    for(var i in obj){//遍历obj的所有可枚举属性,包括实例上的和原型上的
        if(obj.hasOwnProperty(i)){//只拷贝实例上的属性
            newObj[i] = (typeof obj[i]=="object") ? deepCopy(obj[i]):obj[i];//注意
        }
    }
    return newObj;
}
var arr=[1,"old",[1,2,3],function(){return "old";},undefined,new RegExp("old")];
var newArr=deepCopy(arr);
arr[2][2]=4;
newArr[4]="new";
console.log(arr);//[ 1, 'old', [ 1, 2, 4 ], [Function], undefined, /old/ ]
console.log(newArr);//[ 1, 'old', [ 1, 2, 3 ], [Function], 'new', {} ]

注意,深拷贝的实现使用了递归,性能不如浅拷贝。在实际开发中我们应该根据实际情况进行选择。

四 第三方库中的深浅拷贝
(1) jQuery——$.extend():深浅复制

$.extend( [boolean], object1, object2 );

用于将一个或多个对象的内容合并到目标对象。

boolean:默认为false,执行浅拷贝;如果设为true,执行深拷贝。
注意:不支持第一个参数传递 false

 var object1 = {
         apple: 0,
         banana: { weight: 52, price: 100 },
         cherry: 97
         };
     var object2 = {
         banana: { price: 200 },
         durian: 100
     };

     $.extend(object1, object2 );
     console.log(JSON.stringify(object1));//{"apple":0,"banana":{"price":200},"cherry":97,"durian":100}

     //$.extend(true,object1, object2 );
     //console.log(JSON.stringify(object1));//{"apple":0,"banana":{"weight":52,"price":200},"cherry":97,"durian":100}

$.extend()源码

(2)underscore——_.clone():浅复制

var x = {
    a: 1,
     b: { z: 0 }
 };

 var y = _.clone(x);

 console.log(y === x);       // false
 console.log(y.b === x.b)   // true

 x.b.z = 100;
 console.log(y.b.z);         // 100 

_.clone()源码:

_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

(3)lodash:
_.clone(value):浅拷贝

 var objects = [{ 'a': 1 }, { 'b': 2 }];

    var shallow = _.clone(objects);
    console.log(shallow[0] === objects[0]);
    // => true

_.cloneDeep(value):深拷贝

 var objects = [{ 'a': 1 }, { 'b': 2 }];

    var shallow = _.cloneDeep(objects);
    console.log(shallow[0] === objects[0]);
    // => false

JavaScript中的深浅拷贝其实实现了JavaScript中非构造函数的继承。

关于“非构造函数”的继承的更多问题参考:Javascript面向对象编程(三):非构造函数的继承

参考:JavaScript专题之深浅拷贝 #32
深入剖析 JavaScript 的深复制

你可能感兴趣的:(JavaScript)