慎用JavaScript深复制!(js deepClone)

时间:2021-11-20 16:55:53

这原本是*里的一个提问,看到答案后受益良多,于是翻译一下下跟大家分享,原文地址:http://*.com/questions/4459928/how-to-deep-clone-in-javascript

浅复制我就不说了,全部是引用对象,网上列出的深复制的几个例子也都有或多或少的问题,我都试过,某些特殊情况下会出现奇怪的问题,这里摆出一个比较完美的深复制函数(别直接复制了拿去用哦,里面有互动的内容):

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');   
    //一些通过new方式建立的东东可能会类型发生变化,我们在这里要做一下正常化处理
    //比如new String('aaa'), or new Number('444')
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testign that this is DOM
            //如果是dom对象,那么用自带的cloneNode处理
            if (item.nodeType && typeof item.cloneNode == "function") {
                var result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                // it is an object literal		
            //如果是个对象迭代的话,我们可以用for in 迭代来赋值
                result = {};
                for (var i in item) {
                    result[i] = clone( item[i] );
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                //这里解决的是带构造函数的情况,这里要看你想怎么复制了,深得话,去掉那个false && ,浅的话,维持原有的引用,                
                //但是我不建议你去new一个构造函数来进行深复制,具体原因下面会解释
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                //朕不建议你去new它的构造函数
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})

上面的这个函数复制一些{}里的玩意儿一点问题都没有,考虑的情况很全面,很周全。但是,现在还没算完,我们来讨论下,如何复制一个“真正”的对象,比如你通过构造函数建立了个对象:

var User = function(){};
var newuser = new User();

这种情况的话用上面的函数是完全没问题的,所有的对象的属性全部暴露在外,换句话说都是公有的,public的,随便访问的,没有问题。你可以用for in去迭代里面的属性,不管什么类型,去掉那个“false &&”就可以用,完全没问题。但是,这里存在着一个风险--要知道,对象或实例的属性不可能全部是公有的,一旦存在私有变量(原文中叫状态state),你这样的复制便毫无意义,因为会丢失这些数据。比如下面的代码:

function Man() {
    var age = 1;
    this.getAge = function () {
        return age;
    }
    this.grow = function () {
        age += 1;
    }
}
var man = new Man();

里面的age这个东东,就会出现复制不了的情况了。

除此之外,还有另外一个问题,想象一个没有这些私有变量的情况下,这些代码木有问题,但是,一些对象的构造函数是有参数的,比如我要这样建立一个对象:

new User({
   bike : someBikeInstance
});

这种情况下你估计要悲剧,someBikeInstance这货很有可能是在另外的一些上下文中创建的,这个我们获取不来的,至少对于这个函数来说是这样。。

那该咋办呢?其实没什么好的解决办法,要么用for in去迭代,丢掉那些私有变量,要么还是用引用,这些都会出现或多或少的问题,另外一个解决方案是在所有的要被复制的对象里单独建立一个复制函数的属性,就像DOM里的复制节点函数cloneNode一样。

初次翻译,有错误的话,还望谅解!^_^