深度克隆

一、js中的對象

談到對象的克隆,必定要說一下對象的概念。

js中的數據類型分爲兩大類:原始類型和對象類型。(1)原始類型包括:數值、字符串、布爾值、null、undefined(後兩個是特殊的原始值,這裏不做詳細的說明,我的上一篇博客有談到過一些)(2)對象類型包括:對象即是屬性的集合,當然這裏又兩個特殊的對象----函數(js中的一等對象)、數組(鍵值的有序集合)。

好了既然對象分爲這兩類,這兩種類型在複製克隆的時候是有很大區別的。原始類型存儲的是對象的實際數據,而對象類型存儲的是對象的引用地址(對象的實際內容單獨存放,爲了減少數據開銷通常存放在內存中)。ps:說到這裏,大家要知道,對象的原型也是引用對象,它把原型的方法和屬性放在內存當中,通過原型鏈的方式來指向這個內存地址。

二、克隆的概念

淺度克隆:原始類型爲值傳遞,對象類型仍爲引用傳遞。

深度克隆:所有元素或屬性均完全複製,與原對象完全脫離,也就是說所有對於新對象的修改都不會反映到原對象中。

三、淺克隆的表現

1,原始類型

看下面一段代碼:

//數值克隆的表現

var a="1";

var b=a;

b="2";

console.log(a);// "1"

console.log(b);// "2"

 

//字符串克隆的表現

var c="1";

var d=c;

d="2";

console.log(c);// "1"

console.log(d);// "2"

 

//字符串克隆的表現

var x=true;

var y=x;

y=false;

console.log(x);// true

console.log(y);// false

從上面的代碼大家可以看出,原始類型即使我們採用普通的克隆方式仍能得到正確的結果,原因就是原始類型存儲的是對象的實際數據。

2.對象類型

前面說過,函數式一等對象,當然也是對象類型,但是函數的克隆通過淺克隆即可實現

var m=function(){alert(1);};

var n=m;

n=function(){alert(2);};

 

console.log(m());//1

console.log(n());//2

大家能看到,我們直接通過普通賦值的方式,就實現了函數的克隆,並且不會影響之前的對象。原因就是函數的克隆會在內存單獨開闢一塊空間,互不影響。

好了,說了這個特殊的”關係戶“以後,我們來說說普通的”選手“。爲了方便後續的代碼表現,我這裏定義一個複雜的對象類型oPerson。下面看一下對象類型的淺複製有什麼危害:

var oPerson={

    oName:"rookiebob",

    oAge:"18",

    oAddress:{

        province:"beijing"

    },    

    ofavorite:[

        "swimming",

        {reading:"history book"}

    ],

    skill:function(){

        console.log("bob is coding");

    }

};

function clone(obj){

    var result={};

    for(key in obj){

        result[key]=obj[key];

    }

    return result;

}

var oNew=clone(oPerson);

console.log(oPerson.oAddress.province);//beijing

oNew.oAddress.province="shanghai";

console.log(oPerson.oAddress.province);//shanghai

通過上面的代碼,大家能看到,經過對象克隆以後,我修改oNew的地址,發現原對象oPerson也被修改了。這說明對象的克隆不夠徹底,那也就是說深度克隆失敗!

四、深克隆的實現

爲了保證對象的所有屬性都被複制到,我們必須知道如果for循環以後,得到的元素仍是Object或者Array,那麼需要再次循環,直到元素是原始類型或者函數爲止。爲了得到元素的類型,我們定義一個通用函數,用來返回傳入對象的類型。

//返回傳遞給他的任意對象的類

function isClass(o){

    if(o===null) return "Null";

    if(o===undefined) return "Undefined";

    return Object.prototype.toString.call(o).slice(8,-1);

}

PS:Object.prototype.toString.call(o)能直接返回對象的類屬性,形如"[object class]"的字符串,我們通過截取class,並能知道傳入的對象是什麼類型。

 

當然這裏有兩個疑問需要解釋下:

(1)爲什麼不直接用toString方法?這是爲了防止對象中的toString方法被重寫,爲了正確的調用toString()版本,必須間接的調用Function.call()方法

(2)爲什麼不使用typeof來直接判斷類型?因爲對於Array而言,使用typeof(Array)返回的是object,所以不能得到正確的Array,這裏對於後續的數組克隆將產生致命的問題。

萬事俱備,只欠曹操了,下面就正兒八經的開始克隆。

//深度克隆

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){

        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;

}

//返回傳遞給他的任意對象的類

function isClass(o){

    if(o===null) return "Null";

    if(o===undefined) return "Undefined";

    return Object.prototype.toString.call(o).slice(8,-1);

}

var oPerson={

    oName:"rookiebob",

    oAge:"18",

    oAddress:{

        province:"beijing"

    },    

    ofavorite:[

        "swimming",

        {reading:"history book"}

    ],

    skill:function(){

        console.log("bob is coding");

    }

};

//深度克隆一個對象

var oNew=deepClone(oPerson);

 

oNew.ofavorite[1].reading="picture";

console.log(oNew.ofavorite[1].reading);//picture

console.log(oPerson.ofavorite[1].reading);//history book

 

oNew.oAddress.province="shanghai";

console.log(oPerson.oAddress.province);//beijing

console.log(oNew.oAddress.province);//shanghai

從上面的代碼可以看到,深度克隆的對象可以完全脫離原對象,我們對新對象的任何修改都不會反映到原對象中,這樣深度克隆就實現了。

這裏要注意一點的就是:爲什麼deepClone這個函數中的result一定要判斷類型?這裏有一種情況,如果你的result直接是{}對象,我明明傳進去的是一個數組,結果你複製完了以後,變成了一個對象了。

//深度克隆

function deepClone(obj){

    var result={},oClass=isClass(obj);

    // if(oClass==="Object"){

    //     result={};

    // }else if(oClass==="Array"){

    //     result=[];

    // }else{

    //     return obj;

    // }

    for(key in obj){

        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;

}

function isClass(o){

    if(o===null) return "Null";

    if(o===undefined) return "Undefined";

    return Object.prototype.toString.call(o).slice(8,-1);

}

//克隆一個數組

var arr=["a","b","c"];

var oNew=deepClone(arr);

console.log(oNew);//Object {0: "a", 1: "b", 2: "c"}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章