小昱个人博客
欢迎来到小昱的世界

勤学如春起之苗,不见其增,日有所长;辍学如磨刀之石,不见其损,日有所亏
深入理解深拷贝和浅拷贝(JS实现)
  • 首页 > 前端 > JavaScript
  • 作者:小昱
  • 2017年8月30日 14:47 星期三
  • 浏览:113
  • 字号:
  • 评论:0
  • 零、写在前面

    以前对深拷贝和浅拷贝理解不够深刻,最近在阅读jQuery源码,被其代码的精妙深深折服,尤其是$.extend和$.clone的实现,实在是绝妙!

    预热,先谈谈堆与栈

    • :队列优先,先进先出;由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    • :先进后出;动态分配的空间 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。 

    基本类型:Undefined、Null、Boolean、Number 和 String,这5中基本数据类型可以直接访问,他们是按照值进行分配的,存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配。 
    引用类型:即存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。 

    一、对深拷贝和浅拷贝的理解

    简单地说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

    浅拷贝:也就是在对象复制时,只是对对象中的数据成员进行简单的赋值,如果对象中存在动态成员,即指针,浅拷贝就会出现问题,只会赋予其内存指针地址。

    深拷贝:对于深拷贝,针对成员变量存在指针的情况,不仅仅是简单的指针赋值,而是重新分配内存空间。

     

    二、浅拷贝的影响

    首先来看一个例子

    var obj = { a:1, arr: [2,3] };
    var shallowObj = shallowCopy(obj);
    
    function shallowCopy(src) {
      var dst = {};
      for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
          dst[prop] = src[prop];
        }
      }
      return dst;
    }

    这样使用浅拷贝是有问题的。因为浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的,所以浅复制会导致 obj.arr 和 shallowObj.arr 指向同一块内存地址,大概的示意图如下:

    如果对源对象的arr进行赋值,拷贝对象也跟着改变:

    shallowObj.arr[1] = 5;
    obj.arr[1]   // = 5

     

    而深复制则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。这就不会存在上面 obj 和 shallowObj 的 arr 属性指向同一个对象的问题。

    var obj = { a:1, arr: [1,2] };
    var obj2 = deepCopy(obj);

    结果示意如下所示

     

    三、深拷贝的实现方式

    ① JSON解析方式

    // 作者:知乎用户
    // 链接:https://www.zhihu.com/question/23031215/answer/31944721
    // 来源:知乎
    
    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;
    };

    最简单的JSON解析拷贝方式

    var newObj = JSON.parse( JSON.stringigy( oldObj ) );
    function deepCopy(obj) {
        return JSON.parse(JSON.stringify(obj));
    }

    不过这有局限性:

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

    但是这比较简单,大多时候完全可以满足需求了

    ② 递归赋值方式

    // 作者:墨铂桑
    // 链接:https://www.zhihu.com/question/23031215/answer/158703216
    // 来源:知乎
    
    function deepClone(obj) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0; i < obj.length; i++) {
                copy.push(obj[i]);
            }
            return copy;
        }
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }
        if (obj instanceof Object) {
            var copy = {};
            for (var key in obj) { //递归 
                if (obj.hasOwnProperty(key)) {
                    copy[key] = deepClone(obj[key]);
                }
            }
            return copy;
        }
    }
    function deepCopy(obj){
        var o;
        switch(typeof obj){
        case 'undefined': break;
        case 'string'   : o = obj + '';break;
        case 'number'   : o = obj - 0;break;
        case 'boolean'  : o = obj;break;
        case 'object'   :
            if(obj === null){
                o = null;
            }else{
                if(obj instanceof Array){
                    o = [];
                    for(var i = 0, len = obj.length; i < len; i++){
                        o.push(deepCopy(obj[i]));
                    }
                }else{
                    o = {};
                    for(var k in obj){
                        o[k] = deepCopy(obj[k]);
                    }
                }
            }
            break;
        default:
            o = obj;break;
        }
        return o;
    }
    function deepCopy(obj){
        var newobj, obj;
        if (obj.constructor == Object){
            newobj = new obj.constructor();
        }else{
            newobj = new obj.constructor(obj.valueOf());//valueOf()方法返回 Array 对象的原始值
        }
        for(var key in obj){
            if ( newobj[key] != obj[key] ){
                if ( typeof(obj[key]) == 'object' ){
                    newobj[key] = deepCopy(obj[key]);
                }else{
                    newobj[key] = obj[key];
                }
            }
        }
        newobj.toString = obj.toString;
        newobj.valueOf = obj.valueOf;
        return newobj;
    }
    function deepCopy(p,c){
        c = c || {};
        for (var i in p){
            if(p.hasOwnProperty(i)){
                if(typeof p[i] === 'object'){
                    c[i] = Array.isArray(p[i]) ? [] : {};
                    deepCopy(p[i],c[i]);
                }else{
                    c[i] = p[i];
                }
            }
        }
        return c;
    }

    ③ 其他

    ES6定义了Object.assign(...)方法来实现浅复制——它会遍历一个或多个源对象的所有可枚举的自有键并把它们复制到目标对象,最后返回目标对象。

    var newObj = Object.assign( {}, oldObject );

     

    总的来说,现在没有一个统一的标准解决办法来实现深复制。

     

     


    参考资料

    javascript中的深拷贝和浅拷贝

    深入剖析 JavaScript 的深复制

    深入理解JavaScript中的堆与栈 、浅拷贝与深拷贝

     

      您阅读这篇文章共花了:  
    二维码加载中...
    本文作者:小昱      文章标题: 深入理解深拷贝和浅拷贝(JS实现)
    本文地址:http://www.xiaoyulive.top/?post=128
    版权声明:若无注明,本文皆为“小昱个人博客”原创,转载请保留文章出处。
    返回顶部| 首页| 碰碰手气| 捐赠支持| 手机版本|后花园

    Copyright © 2016-2017 小昱个人博客 滇ICP备16006294号