1

什么是浅拷贝和深拷贝

浅拷贝

浅拷贝:将一个对象自身的属性拷贝给另一个对象,如果源对象的属性是基本类型则直接进行值赋值,如果是引用类型则进行引用赋值,也就是说只进行一层赋值。

深拷贝

深拷贝:将一个对象自身的属性拷贝给另一个对象,如果源对象的属性是基本类型则直接进行值赋值,如果是引用类型则复制这个引用类型,使得目标对象拥有一个引用类型且和这个源属性一模一样,而非是一个指针。

也就是说,深拷贝与浅拷贝最主要的区别在引用类型的拷贝上。

注意,引用赋值不是浅拷贝!! 引用赋值仅仅只是赋值个指针,两个变量都指向同一内存区域,而浅拷贝是使得两个变量分别指向不同的内存区域

如果不懂,可以参考这里 一个小姐姐的博客

以上不是重点...

浅拷贝的实现

方法一 使用 for in 遍历

function shallowCopy(source){
    var target=source instanceof Array ? [] : {};
    for(var i in source){
        if(source.hasOwnProperty(i)){
            target[i]=source[i];
        }
    }
    return target;
}

// 测试
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar=shallowCopy(obj)
tar.c()         // "i am c"
obj.a=5
obj.a           // 5
tar.a           // 1
obj.b[0]=10
obj.b           // [10, 2, 3]
tar.b           // [10, 2, 3]

var arr=[1,2,[4,5,6]]
var newArr=shallowCopy(arr)
newArr          // [1, 2, [4,5,6]]
arr[0]=10
arr             // [10, 2, [4,5,6]]
newArr          // [1, 2, [4,5,6]]
arr[2][0]=10
arr             // [1, 2, [10,5,6]]
newArr          // [1, 2, [10,5,6]]

方法二 使用 Object.assign 或 slice、concat

Object.assign

var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar={};
Object.assign(tar,obj);

当然这个方法只适合于对象类型,如果是数组可以使用slice和concat方法

Array.prototype.slice

var arr=[1,2,[3,4]];
var newArr=arr.slice(0);

Array.prototype.concat

var arr=[1,2,[3,4]];
var newArr=arr.concat();

测试同上(assign用对象测试、slice concat用数组测试),结合浅拷贝深拷贝的概念来理解效果更佳

深拷贝的实现

方法一 JSON黑科技

var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar=JSON.parse(JSON.stringify(obj));

// 测试
obj.a=5
obj.a     // 5
tar.a     // 1
obj.b[0]=10
obj.b     // [10, 2, 3]
tar.b     // [1, 2, 3]
tar.c()   // Uncaught TypeError: tar.c is not a function

可以看到,无论是基本类型还是引用类型,两个对象的相同属性(属性名相同的属性)之间并没有关系了,但tar.c()报错了,我们打印看一下tar有什么console.log(tar) // {a: 1, b: Array(3)}可以看到c方法没了,这是和JSON的语法有关,JSON 并不支持函数类型的数据。这也就是这种方法的最大缺陷。

仔细一看,这并非黑科技,反而倒是有很大缺陷,不过很好用、效率高,只是在使用前需要稍加注意是否有函数类型的数据罢了。

方法二 递归拷贝

深拷贝与浅拷贝相比不就是多拷贝几层的事嘛,这不就是递归常干的事嘛。所以我们就想,在每次拷贝时都判断下,该属性是否是引用类型,如果是我们再递归调用拷贝方法,否则直接进行值赋值。

function deepCopy(obj,tar){
    var tar = tar || {};
    for(var i in obj){
        if(typeof obj[i] === 'object'){
            if(obj[i].constructor === Array){
                tar[i] =[];
            }else{
                tar[i] = {};
            }
            deepCopy(obj[i],tar[i]);
          }
        else{
              tar[i] = obj[i];
          }
    }
    return tar;
}

// 使用
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}};
var tar={};
deepCopy(obj,tar);
console.log(tar);

测试同上,你会惊喜的发现第一个方法中的函数bug没了

你看出来函数是怎样进行拷贝的了吗?很简单,typeof运算符的操作对象是一个函数时,得到的是 "function" 所以在循环里第一个if判断那为false 所以走else分支,在tar[i] = obj[i]这里,函数是进行引用赋值的,如果再造一个相同的函数不是不可以,只是不符合思想罢了,函数占用堆内存,如果可以共用当然是最好的选择。

当然这个方法还是有些许不足之处,不够已经很棒了

方法三 更严谨、更优雅的实现

这就需要去读一些好的源码了,比如Zepto、JQuery中extend方法的实现

现学现卖,如有不足请指出...


None_
11 声望0 粉丝

会成为盖世英雄,没有七彩祥云


« 上一篇
L - 居中布局