• js对象的深度克隆


    在聊JavaScript(以下简称js)深度克隆之前,我们先来了解一下js中对象的组成。
    在 js 中一切实例皆是对象,具体分为 原始类型 和 合成类型 :
    原始类型 对象指的是 Undefined 、 Null 、Boolean 、Number 和 String ,按值传递。
    合成类型 对象指的是 array 、 object 以及 function ,按址传递,传递的时候是内存中的地址。

    克隆或者拷贝分为2种: 浅度克隆 、 深度克隆 。
    浅度克隆 :基本类型为值传递,对象仍为引用传递。
    深度克隆 :所有元素或属性均完全克隆,并于原引用类型完全独立,即,在后面修改对象的属性的时候,原对象不会被修改。

    又或许你刚听说“深度克隆”这个词,简单来说,就是说有个变量a,a的值是个对象(包括基本数据类型),现在你要创建一个变量b,使得它拥有跟a一样的方法和属性等等。但是a和b之间不能相互影响,即a的值的改变不影响b值的变化。直接赋值可好?

    var a = 1;
    var b = a;
    a = 10;
    console.log(b);            // 1
     
    var a = 'hello';
    var b = a;
    a = 'world';
    console.log(b);            // hello
     
    var a = true;
    var b = a;
    a = false;
    console.log(b);            // true

    实践证明某些 JavaScript 的原始数据类型,如果要克隆直接赋值即可。
    关于 function 的深度复制:查阅了一些资料, function 的深度复制似乎和原始数据类型的深度复制一样。

    var a = function () {
        console.log(1);
    };
    var b = a;
    a = function () {
        console.log(2);
    };
    b(); 

    本来我也是这么认为的,直到文章下出现了评论。思考后我觉得 function 和普通的对象一样,只是我们在平常应用中习惯了整体的重新赋值,导致它在深度复制中的表现和原始类型一致:

    var a = function () {
        console.log(1);
    };
    a.tmp = 10;
    var b = a;
    a.tmp = 20;
    console.log(b.tmp);        // 20

    于是乎对于 function 类型的深度克隆,直接赋值似乎并不应该是一种最好的方法(尽管实际应用中足矣)。

    但是对象呢?

    var a = [0,1,2,3];
    var b = a;
    a.push(4);
    console.log(b);            // [0, 1, 2, 3, 4]

    显然与预期不符,为什么会这样?因为原始数据类型储存的是对象的实际数据,而对象类型存储的是对象的引用地址。上面的例子呢也就是说a和b对象引用了同一个地址,无论改变a还是改变b,其实根本操作是一样的,都是对那块空间地址中的值的改变。

    于是我们知道了,对于基本的对象来说,不能只能用 “ = ” 赋值,思索后写下如下代码:

    // 判断arr是否为一个数组,返回一个bool值
    function isArray (arr) {
        return Object.prototype.toString.call(arr) === '[object Array]';  
    }
    // 深度克隆
    function deepClone (obj) {  
        if(typeof obj !== "object" && typeof obj !== 'function') {
            return obj;        //原始类型直接返回
        }
        var o = isArray(obj) ? [] : {}; 
        for(i in obj) {  
            if(obj.hasOwnProperty(i)){ 
                o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i]; 
            } 
        } 
        return o;
    }

    注意代码中判断数组的时候用的不是 obj instanceof Array ,这是因为该方法存在一些小问题,详情见http://www.nowamagic.net/librarys/veda/detail/1250

    用一些代码来测试下:

    // 测试用例:
    var srcObj = {
        a: 1,
        b: {
            b1: ["hello", "hi"],
            b2: "JavaScript"
        }
    };
    var abObj = srcObj;
    var tarObj = cloneObject(srcObj);
    
    srcObj.a = 2;
    srcObj.b.b1[0] = "Hello";
    
    console.log(abObj.a);
    console.log(abObj.b.b1[0]);
    
    console.log(tarObj.a);      // 1
    console.log(tarObj.b.b1[0]);    // "hello"

    对于上面的方法再进行测试下,如下:

    这个没有区分具体的对象,在此问下大家js的对象有哪些呢?相信一般人答不出来4个
    [object Object][object Array][object Null][object RegExp][object Date][object HTMLXXElement][object Map],[object Set],... 等等一系列

    检测类型使用 Object.prototype.toString.call(xxx) 和 typeof

    我们分析下上面对象中哪些是引用类型需要特殊处理呢?相信大家都不陌生了。[object Object] 和 [object Array]

    好!详细大家思路有了,咋们用递归来实现一把吧!

    const deepClone = function(obj) {
      // 先检测是不是数组和Object
      // let isMap = Object.prototype.toString.call(obj) === '[object Map];
      // let isSet = Object.prototype.toString.call(obj) === '[object Set];
      // let isArr = Object.prototype.toString.call(obj) === '[object Array]';
      let isArr = Array.isArray(obj);
      let isJson = Object.prototype.toString.call(obj) === '[object Object]';
      if (isArr) {
        // 克隆数组
        let newObj = [];
        for (let i = 0; i < obj.length; i++) {
          newObj[i] = deepClone(obj[i]);
        }
        return newObj;
      } else if (isJson) {
        // 克隆Object
        let newObj = {};
        for (let i in obj) {
          newObj[i] = deepClone(obj[i]);
        }
        return newObj;
      }
      // 不是引用类型直接返回
      return obj;
    };
    
    Object.prototype.deepClone = function() {
      return deepClone(this);
    };

    注:先不考虑Map Set Arguments [object XXArrayBuffer] 对象了原理都是一样

    各种情况分析完了才说算是真克隆
    我们在控制台看下

      • 注意先要把方法在控制台输进去,在调试

     

    是不是解决了? 在此并没有结束。 专注的伙伴们相信发现了对象中包含了个 deepClone 方法,具体细节我们在此就不多说了,我们给 Object 添加了个 Object.prototype.deepClone方法导致了每个对象都有了此方法。

    原则上我们不允许在原型链上添加方法的,因为在循环中 for inObject.entriesObject.valuesObject.keys等方法会出现自定义的方法。

    相信熟悉 Object 文档的伙伴人已经知道解决方案了,

    Object.defineProperty 这个方法给大家带来了福音 具体参考 Object 文档。我们使用一个enumerable (不可枚举)属性就可以解决了。

    在原来基础上添加以下代码即可。

    Object.defineProperty(Object.prototype, 'deepClone', {enumerable: false});

    再看控制台

    同样上面方法中也是无法克隆一个不可枚举的属性。

    完整代码如下:

    const deepClone = function(obj) {
      // 先检测是不是数组和Object
      // let isArr = Object.prototype.toString.call(obj) === '[object Array]';
      let isArr = Array.isArray(obj);
      let isJson = Object.prototype.toString.call(obj) === '[object Object]';
      if (isArr) {
        // 克隆数组
        let newObj = [];
        for (let i = 0; i < obj.length; i++) {
          newObj[i] = deepClone(obj[i]);
        }
        return newObj;
      } else if (isJson) {
        // 克隆Object
        let newObj = {};
        for (let i in obj) {
          newObj[i] = deepClone(obj[i]);
        }
        return newObj;
      }
      // 不是引用类型直接返回
      return obj;
    };
    
    Object.prototype.deepClone = function() {
      return deepClone(this);
    };
    Object.defineProperty(Object.prototype, 'deepClone', {enumerable: false});

    注: 为了兼容低版本浏览器需要借助 babel-polyfill;

     附: 其他深拷贝方式选择:https://blog.csdn.net/ios99999/article/details/77646594

    一维数据结构的深拷贝方法建议使用:Object.assign();

    二维数据结构及以上的深拷贝方法建议使用:JSON.parse(JSON.stringify());

    特别复杂的数据结构的深拷贝方法建议使用:Loadsh.cloneDeep();

    JSON.parse(JSON.stringify(obj))是最简单粗暴的深拷贝,能够处理JSON格式的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝,而且会直接丢失相应的值,还有就是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理:

    var obj = { a: {a: "hello"}, b: 33 };
    var newObj = JSON.parse(JSON.stringify(obj));
    newObj.b = "hello world";
    console.log(obj);    //  { a: "hello", b: 33 };
    console.log(newObj);    //  { a: "hello world", b: 33};
    console.log(obj==newObj);  //  false
    console.log(obj===newObj);  //  false

    参考链接:

    https://segmentfault.com/a/1190000014336441

    https://segmentfault.com/a/1190000014336441

  • 相关阅读:
    处理不均衡数据 (Imbalanced data)
    过拟合 (Overfitting)
    加速神经网络训练 (Speed Up Training)
    Python3 S.join() 个人笔记
    OpenCV中的图像形态学转换
    OpenCV中的绘图函数
    OpenCV中图像的BGR格式及Img对象的属性说明
    OpenCV中图像的读取,显示与保存
    msyql-02
    jumpserver的快速部署
  • 原文地址:https://www.cnblogs.com/momo798/p/9235128.html
Copyright © 2020-2023  润新知