JS对象深度克隆

javascript的一切实例都是对象,只是对象之间稍有不同,分为原始类型和合成类型。原始类型对象指的是字符串(String)、数值(Number)、布尔值(Boolean),合成类型对象指的是数组(Array)、对象(Object)、函数(Function)。

既然对象分为这两类,他们之间的最大差别是复制克隆的差别。普通对象存储的是对象的实际数据,而引用对象存储的是对象的引用地址,而把对象的实际内容单独存放,因为引用对象通常比较庞大,这是数据开销和内存开销优化的手段。通常初学者很难理解这部分内容,就像对象的原型一样,也是同一个概念。对象的原型也是引用对象,把原型的方法和属性放在单独内存当中,而对象的原型链则指向这个内存地址。尽管这部分内容比较拗口复杂,那其中的原理都是一致的,目的也一致。

    var x = "1";
    var y = x;
    y = "2";
    
    console.log(x); // "1"
    console.log(y); // "2"
    

    1.2、数值的克隆

    var x = 1;
    var y = x;
    y = 2;
    
    console.log(x); // 1
    console.log(y); // 2
    

    1.3、布尔值的克隆

    var x = true;
    var y = x;
    y = false;
    
    console.log(x); // true
    console.log(y); // false
    

    2、合成类型对象的克隆 2.1、数组的克隆

    如果采用普通克隆:

    var x = [1, 2];
    var y = x;
    y.push(3);
    
    console.log(x); // 1,2,3
    console.log(y); // 1,2,3
    

    由上可知,原始数组x,克隆数组y,修改了克隆数组y,但也同时修改了原始数组x,这就是引用对象的特点。那么如何才能达到完整的数组克隆呢?

    var x = [1, 2];
    var y = [];
    var i = 0;
    var j = x.length;
    for(; i<j; i++)
    {
        y[i] = x[i];
    }
    y.push(3);
    
    console.log(x); // 1,2
    console.log(y); // 1,2,3
    

    这样,克隆数组y,原始数组x,两个数组互补干扰,实现了完整的数组克隆。

    2.2、对象的克隆

    和数组的克隆同理,对象的完整克隆如下:

    var x = {1:2, 3:4};
    var y = {};
    var i;
    for(i in x)
    {
        y[i] = x[i];
    }
    y[5] = 6;
    
    console.log(x); // Object {1: 2, 3: 4}
    console.log(y); // Object {1: 2, 3: 4, 5: 6}
    

    2.3、函数的克隆

    var x = function(){console.log(1);};
    var y = x;
    y = function(){console.log(2);};
    
    console.log(x); // function(){console.log(1);};
    console.log(y); // y=function(){console.log(2);};
    

    函数的克隆,使用“=”符号就可以了,并且在改变克隆后的对象,不会影响克隆之前的对象,因为克隆之后的对象会单独复制一次并存储实际数据的,是真实的克隆。

    3、完整的对象克隆 根据1和2,总结一下完整的对象克隆,包括克隆普通对象、引用对象。在写这个方法之前,我们必须想到的是,克隆引用对象必须采用完整克隆(深度克隆),包括对象的值也是一个对象也要进行完整克隆(深度克隆)。

    完整的对象克隆又称为深度对象克隆、对象的深度克隆、对象的深度复制等等。

    function clone(obj){
        var o = null;
        
        /* 等号克隆:undefined, null, Boolean, String, Number, Function */
        if(typeof(obj) != 'object' || obj === null){
            return obj;
        }
        
        /* 递归克隆:Array */
        if(Object.prototype.toString.call(obj) == '[object Array]'){
            o = [];
            for(var i=0, len=obj.length; i<len; i++){
                if(typeof(obj[i]) == 'object' && obj[i] != null){
                    o[i] = arguments.callee(obj[i]);
                }else{
                    o[i] = obj[i];
                }
            }
        }
        
        /* 递归克隆:Object */
        if(Object.prototype.toString.call(obj) == '[object Object]'){
            o = {};
            for(var item in obj){
                if(typeof(item) == 'object' && obj[i] != null){
                    o[item] = arguments.callee(obj[item]);
                }else{
                    o[item] = obj[item];
                }
            }
        }
        
        return o;
    }
    

    测试下面的对象:

    var obj = {
        a: undefined,
        b: null,
        c: false,
        d: 1023,
        e: 'Hello, clone.',
        f: function(){
            console.log(this.a + ', ' + this.b + ', ' + this.c + ', ' + this.d + ', ' + this.e + ', ' + this.f + ', ' + this.g + '. ');
            return true;
        },
        g: {
            bool: true,
            func: function(){
                return this.bool;
            }
        },
        h: ['a', 'cc', 'gg', 'kkk', 'nnn']
    };
    
    var objClone = clone(obj);
    

    结果:

    >>obj
    Object { a: undefined, b: null, c: false, d: 1023, e: "Hello, clone.", f: obj.f(), g: Object, h: Array[5] }
    >>objClone
    Object { a: undefined, b: null, c: false, d: 1023, e: "Hello, clone.", f: obj.f(), g: Object, h: Array[5] }
    

    4、参考资料 http://javascript.ruanyifeng.com/grammar/basic.html#toc9