美文网首页前端面试前端文章翻译
JS中的深拷贝,来自stackoverflow的高票答案

JS中的深拷贝,来自stackoverflow的高票答案

作者: mervynyang | 来源:发表于2016-08-30 11:33 被阅读173次

    深拷贝JS中的任何对象都不是一件容易的事情,你将会遇到这样的问题,从object的原型中,错误的选择应该留在原型上而不是拷贝到新实例上的属性。举个例子,你添加了一个clone的方法到Object.prototype,正如其他答案描述的,你需要显式地跳过该属性。但是如果其他额外的方法或者中间原型也添加到了Object.prototype,而你不知道。在这种情况下,你将会拷贝你不需要的属性,所以你需要用hasOwnProperty方法检查不可预见的、非局部的属性。

    除了不可枚举的属性之外,你将会遇到更难的一个问题,就是当你拷贝一个拥有隐藏属性的对象。举个例子,函数的prototype是隐藏的,对象原型的引用__proto__属性也是隐藏的,通过for/in的迭代方法将不能拷贝源对象上的这些属性。我认为Firefox的JS解释器的__proto__属性是比较特殊的,可能跟其他的浏览器有点不同,但是你可以想到,不是一切都是可枚举的。如果你知道属性的名字,那你就能够拷贝这个属性,但是我不知道怎么去自动发现这些隐藏的属性。

    另一个障碍是寻找一个优雅的解决方案,正确的设置原型的继承。如果你的源对象是一个Object,那么简单的用{}创建一个普通的对象也会工作。但是如果源对象上的原型是某些Object的后代,那么你使用hasOwnProperty方法过滤的时候,或者在原型链开始的地方是不能枚举的,将会跳过原型,丢失一些额外的成员。一个解决方法是调用源对象上的constructor的属性得到初始化的拷贝对象,然后拷贝属性,不过你仍然不能得到不可枚举的属性。例如,一个Date对象存储的数据是隐藏的。

        function clone(obj) {
            if (obj === null || typeof obj !== 'object') {
                return obj;
            }
    
            var copy = obj.constructor();
    
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) {
                    copy[attr] = obj[attr];
                }
            }
    
            return copy;
        }
    
        var d1 = new Date();
    
        // 等五秒钟
        var start = (new Date()).getTime();
        while ((new Date().getTime()) - start < 5000);
    
        var d2 = clone(d1);
    
        console.log('d1=' + d1.toString() + 'd2=' + d2.toString());
        // d1=Tue Aug 30 2016 10:19:59 GMT+0800 (CST)d2=Tue Aug 30 2016 10:20:04 GMT+0800 (CST)
    

    d2的值将会比d1的值大5秒钟。有一个让Date类型与另一个Date类型相同的办法,就是调用setTime方法,但是这只是对Date类而言的。我认为这不是一个万无一失的方法,我很乐意是错的!

    当我必须实现一个通用的深拷贝的方法时,我最终还是妥协了,假设我只有纯Object、Array、Date、String、Number或者Boolean类型需要拷贝。最后3个类型是不可变的,所以我能够执行浅拷贝而不用担心它改变了。我进一步假设任何包含对象或数组的元素也将是这6个简单类型其中的一个,这可以用下面的代码来实现:

        function clone(obj) {
            var copy;
            
            // 处理3个简单的类型, null 或者 undefined
            if (obj === null || typeof obj !== 'object') {
                return obj;
            }
    
            if (obj instanceof Date) {
                copy = new Date();
                copy.setTime(obj.getTime());
                return copy;
            }
    
            if (obj instanceof Array) {
                var copy = [];
                for (var i = 0, len = obj.length; i < len; i++) {
                    copy[i] = clone(obj[i]);
                }
                return copy;
            }
    
            if (obj instanceof Object) {
                var copy = {};
                for (var attr in obj) {
                    if (obj.hasOwnProperty(attr)) {
                        copy[attr] = clone(obj[attr]);
                    }
                }
                return copy;
            }
    
            throw new Error("Unable to copy obj! Its type isn't supported.");
        }
    

    上面的方法完全能够在我提到的那6个简单类型中工作,只要对象和数组中的数据形成一个树状结构,也就是说,在1个对象中没有多于1个的对相同数据的引用。例如:

        // 这是可以克隆的
        var tree = {
            "left": { "left": null, "right": null, "data": 3 },
            "right": null,
            "data": 8
        };
        
        // 这样也可以工作,但是你会得到2份内部节点,而不是2个引用相同的副本
        var directedAcyclicGraph = {
            "left"  : { "left" : null, "right" : null, "data" : 3 },
            "data"  : 8
        };
    
        directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
        
        // 这种情况因为无限的递归,会导致堆栈溢出
        var cylicGraph = {
            "left"  : { "left" : null, "right" : null, "data" : 3 },
            "data"  : 8
        };
        cylicGraph["right"] = cylicGraph;
    

    这个clone的方法不能处理所有的JS对象,但已经能满足大部分的需求了,只要你不把所有的工作的丢给它就可以了。

    原文地址:How do I correctly clone a JavaScript object?

    相关文章

      网友评论

        本文标题: JS中的深拷贝,来自stackoverflow的高票答案

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