首页 > 代码库 > JavaScript之深浅拷贝

JavaScript之深浅拷贝

数组的浅拷贝

  如果是数组,我们可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝。比如:

var arr = [old, 1, true, null, undefined];
var new_arr = arr.concat();
new_arr[0] = new;
console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]
var arr = [old, 1, true, null, undefined];
var new_arr = arr.slice();
new_arr[0] = new;
console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]

  concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本,即返回一个新的数组。语法:arrayObject.concat(arrayX,......,arrayX),该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组

  slice() 方法从已有的数组中返回选定的元素。语法:arrayObject.slice(start,end),返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。请注意,该方法并不会修改数组,而是返回一个子数组。如果想删除数组中的一段元素,应该使用方法 Array.splice()。

  注释:1、您可使用负值从数组的尾部选取元素。2、如果 end 未被规定,那么 slice() 方法会选取从 start 到数组结尾的所有元素。

  但是,如果数组嵌套了对象或者数组的话,比如:

var arr = [{old: old}, [old]];
var new_arr = arr.concat();
arr[0].old = new;
arr[1][0] = new;
console.log(arr) // [{old: ‘new‘}, [‘new‘]]
console.log(new_arr) // [{old: ‘new‘}, [‘new‘]]

  我们会发现,无论是新数组还是旧数组都发生了变化,也就是说使用 concat 方法,克隆的并不彻底。这里可以联系之前写的值传递和引用传递、栈内存和堆内存的问题:如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化

  我们把这种复制引用的拷贝方法称之为浅拷贝,与之对应的就是深拷贝,深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。所以我们可以看出使用 concat 和 slice 是一种浅拷贝。

数组的深拷贝

  那如何深拷贝一个数组呢?这里介绍一个技巧,不仅适用于数组还适用于对象!那就是:

var arr = [old, 1, true, [old1, old2], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
new_arr[3][0] = new;
new_arr[4].old = 2;
console.log(arr,new_arr);

  是一个简单粗暴的好方法,就是有一个问题,不能拷贝函数,我们做个试验:

技术分享

浅拷贝的实现

  以上三个方法 concat、slice、JSON.stringify 都算是技巧类,可以根据实际项目情况选择使用,接下来我们思考下如何实现一个对象或者数组的浅拷贝。

  想一想,好像很简单,遍历对象,然后把属性和属性值都放在一个新的对象不就好了~嗯,就是这么简单,注意几个小点就可以了:

var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== object) return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
var arr = [{old:"old"},["old"]];
var newarr = shallowCopy(arr);
console.log(newarr);//[{old:"new"},["old"]]
newarr[0].old = new;
console.log(arr,newarr);//[{old:"new"},["old"]],     [{old:"new"},["old"]]

深拷贝的实现

  那如何实现一个深拷贝呢?说起来也好简单,我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数不就好了~

var deepCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== object) return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
           /* if(typeof obj[key] === "object"){
                newObj[key] = deepCopy(obj[key]);
            }else{
                newObj[key] = obj[key];
            }*/
           newObj[key] = typeof obj[key] === "object" ? deepCopy(obj[key]) : obj[key]
        }
    }
    return newObj;
}
var arr = [{old:"old"},["old"]];
var newarr = deepCopy(arr);
console.log(newarr);//[{old:"new"},["new"]]
newarr[0].old = new;
newarr[1][0] = new;
console.log(arr,newarr);//[{old:"old"},["old"]],[{old:"new"},["new"]]

性能问题

  尽管使用深拷贝会完全的克隆一个新对象,不会产生副作用,但是深拷贝因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。

JavaScript之深浅拷贝