美文网首页
深度探讨-js深克隆

深度探讨-js深克隆

作者: Taniffer | 来源:发表于2017-09-07 16:38 被阅读0次

    js深克隆一直是一个很奇妙的问题。也是面试中常问的一个问题。但是可能大多数人的解决方案都是有问题的。但是并不是解决方案本身的问题,是这个问题本身的问题。从一个角度来看,这个问题其实是无解的。但是换一个角度,这个问题已然有了最优解。下面就来说一下这个问题的各种** 解决方案 **。

    1.使用JSON

    JSON.parse(JSON.stringify(obj))
    

    看一个例子

    obj={a:1,b:{b:1}}
    JSON.parse(JSON.stringify(obj))//obj={a:1,b:{b:1}}
    

    深克隆完毕。。是不是觉得很简单,很惊艳,从某种程度来说,没错,这种方法是很好,而且如果是简单结构的话比jquery的extend效率要高10%-20%。但是问题出在哪里呢,JSON的深克隆不会克隆NAN,undefined这些,更别说function,Data了。所以又有了递归遍历这种操作。

    2.递归

    //返回传递给他的任意对象的类
        function isClass(o) {
            return Object.prototype.toString.call(o).slice(8, -1);
        }
    
        //深度克隆
        function deepClone(obj) {
            var result, oClass = isClass(obj);
            //确定result的类型
            if (oClass === "Object") {
                result = {};
            } else if (oClass === "Array") {
                result = [];
            } else {
                return obj;
            }
            for (key in obj) {
                if (obj.hasOwnProperty(key)) { //原型链属性不克隆
                    var copy = obj[key];
                    if (isClass(copy) === "Object") {
                        result[key] = arguments.callee(copy);//递归调用
                    } else if (isClass(copy) === "Array") {
                        result[key] = arguments.callee(copy);
                    } else {
                        result[key] = obj[key];
                    }
                }
            }
            return result;
        }
    
    

    突然就感觉解决了所有问题吧。数组,和对象的情况都考了到了呢。而且原型链上的繁杂东西也没有带出来。是不是觉得又完美了。
    然而,打击总是一直存在的,在已知数据结构的情况下你可以这样使用,但是,如果不知道就不行。比如。如果有环?然后就有了死循环,无法避免的栈溢出报错。。。
    而且,原型链也没有复制,从一定程度来说也不算深克隆。。那如果已知数据结构,还不如直接用for in循环来的快一些。。

    所以说真正的深克隆需要考虑的问题有这些

    1.JSON 克隆不支持函数、引用、undefined、Date、RegExp 等
    2.递归克隆要考虑环
    3.要考虑 等特殊对象的克隆方式
    4.要不要克隆 proto,如果要克隆,就非常浪费内存;如果不克隆,就不是深克隆。

    3.JQuery中较为完美的实现

    仔细想想这些问题可能会爆炸。。。。看看jquery的那些大佬怎么实现的吧。。代码挺长的,这里有一个相近的实现。 下面的代码可以先跳过。

    $ = function() {  
        var copyIsArray,  
            toString = Object.prototype.toString,  
            hasOwn = Object.prototype.hasOwnProperty;  
      
        class2type = {  
            '[object Boolean]' : 'boolean',  
            '[object Number]' : 'number',  
            '[object String]' : 'string',  
            '[object Function]' : 'function',  
            '[object Array]' : 'array',  
            '[object Date]' : 'date',  
            '[object RegExp]' : 'regExp',  
            '[object Object]' : 'object'  
        },  
      
        type = function(obj) {  
            return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";  
        },  
      
        isWindow = function(obj) {  
            return obj && typeof obj === "object" && "setInterval" in obj;  
        },  
      
        isArray = Array.isArray || function(obj) {  
            return type(obj) === "array";  
        },  
      
        isPlainObject = function(obj) {  
            if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {  
                return false;  
            }  
      
            if (obj.constructor && !hasOwn.call(obj, "constructor")  
                    && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {  
                return false;  
            }  
      
            var key;  
            for (key in obj) {  
            }  
      
            return key === undefined || hasOwn.call(obj, key);  
        },  
      
        extend = function(deep, target, options) {  
            for (name in options) {  
                src = target[name];  
                copy = options[name];  
      
                if (target === copy) { continue; }  
      
                if (deep && copy  
                        && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {  
                    if (copyIsArray) {  
                        copyIsArray = false;  
                        clone = src && isArray(src) ? src : [];  
      
                    } else {  
                        clone = src && isPlainObject(src) ? src : {};  
                    }  
      
                    target[name] = extend(deep, clone, copy);  
                } else if (copy !== undefined) {  
                    target[name] = copy;  
                }  
            }  
      
            return target;  
        };  
      
        return { extend : extend };  
    }();  
    

    与之前相比。多了这些有趣的代码

    if (target === copy) { continue; }  //避免了死循环
    

    在isPlainObject函数中

    if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {  
        return false;  
    } 
    //以下情况不复制
      1. 对象为undefined; 
      2. 转为String时不是"[object Object]"; 
      3. obj是一个DOM元素; 
      4. obj是window。
    
    

    之所以不对DOM元素和window进行深复制,可能是因为它们包含的属性太多了;尤其是window对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。

    if (obj.constructor && !hasOwn.call(obj, "constructor")  
                  && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {  
          return false;  
      }  
    //如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过prototye继承来的,这种情况也不进行深复制。
    var key;  
    for (key in obj) {  
    }  
    return key === undefined || hasOwn.call(obj, key);  
    // 这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,
    会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。
    

    上面的代码和和jQuery的实现就很相似。做到了,各种类型的判断,死循环的避免,而对于复杂的原型拷贝问题,它做了部分浅复制(关于原型做深复制很不必要,而且浪费大量时间和内存)。大部分情况下是深拷贝,也是一种较为完美的解决方案了。

    4.优雅实现-immutable.js

    深复制已经解决到这个层面了,可以说在这个角度没有更优的解决方案了吧。回归到问题本身。我们为什么要深复制这个对象。如果要求一样,直接使用原来的对象不行吗?真的不一样的话,为什么不新建一个对象,为什么要用这么浪费性能和内存的深复制?可能你复制之后的对象的大部分属性还是没有用的。这就牵扯到了一个js库。。。

    在React出来的同时出了一个js库-immutable.js,怎么说呢,它真的是一个很厉害的库,也是一个很厉害的想法,但是react优点遮挡了他的光辉吧。这也是一个react的官方推荐库。一般它会和react一起使用,但是它单独也可以使用,它是一种结构复用的思想。也就是数据不可变,什么意思呢,就是用immutable建立的对象不会被改变,会产生一个新分支来共用它的结构,下图这样。

    immutable.gif

    具体了解的话来看一下这两篇文章。
    Immutable 详解及 React 中实践
    官方文档

    const { Map } = require('immutable')
    const map1 = Map({ a: 1, b: 2, c: 3 })
    const map2 = map1.set('b', 50)
    map1.get('b') // 2
    map2.get('b') // 50
    

    这种结构复用的解决方案基本上已经脱离了深复制的层面,不是从解决深复制,而是单纯的不让深复制产生。从根源上解决了这个问题,实现了时间复杂度和空间复杂度的双优化。但是对于已有的含复杂原型链的对象,可能它也没有太好的解决方法

    相关文章

      网友评论

          本文标题:深度探讨-js深克隆

          本文链接:https://www.haomeiwen.com/subject/fdobjxtx.html