javascript深度拷貝分析

先來介紹一下深拷貝是如何而來的。

深拷貝是對比與淺拷貝而言的,我們都知道js中分爲基本類型以及引用類型,基本類型都是保存在棧裏面的,而引用類型僅僅是把地址保存在棧裏面,而值是存在於堆裏面,淺拷貝僅僅是拷貝棧裏面東西;

也就是說對於基本類型而言在棧裏面重新開闢一個空間保存複製後的值和址,而對於引用類型而言僅僅是把地址複製過來,而這個地址指向的依然是原來址指向的那個堆裏面的值,這樣一來如果我們淺複製的話對於基本類型而言是沒問題的,

而對於引用類型來說 就相當於兩個地址同時指向了一個值,修改兩個地址裏面任意一個的值都會對另一個造成影響。看一段代碼:

var a={

  name:'yuchao',

  age:'26'

}

var b=a;

b.sex="man";

console.log(a);//{name: "yuchao", age: "26", sex: "man"}

console.log(b);//{name: "yuchao", age: "26", sex: "man"}

可以看到輸出的是同樣結果,一般情況下這都不是我們想要的結果,所以我們引進了能將對象等引用類型重新指向的深拷貝。

如何實現深度拷貝

對於實現深拷貝方法很多,我們來慢慢看:

1.json轉化

對於高級瀏覽器(ie9+firefox、chrome等)裏面有內置的JSON對象以及stringify和parse方法對字符串以及object之間進行互相轉化。

這個轉化過程我們可以加以利用,因爲每一次轉化都是重新定義一個新空間。這種方式很好理解,對一個Object對象而言,先使用內置的JSON.stringify()函數,將其轉化爲json字符串。

此時生成的字符串已經和原對象沒有任何聯繫了,再通過JSON.parse()函數,將生成的字符串轉化爲一個新的對象。

而在新對象上的操作與舊對象是完全獨立的,不會相互影響。實現代碼也是簡單易懂,並且利用js內置函數不需要過多內存開銷。

function deepCopy(json){

  return JSON.parse(JSON.stringify(json));

}

舉例:var person={name:'yuchao',age:'26',getName:function(){alert(this.name)}};

var newPerson=deepCopy(person);

newPerson.name='xiaopu';

console.log(person);//Object {name: "yuchao", age: "26",getName:function(){alert(this.name)}}

console.log(newPerson);//Object {name: "xiaopu", age: "26"}

在上面代碼中,我們可以看到通過深拷貝newPerson以及跟person兩個對象毫無關係了,修改newPerson的值對原對象沒有造成任何影響。這對於簡單的深拷貝是沒有問題的。不過我們都注意到了newPerson中的getName去哪裏了?

原因是通過json的stringify以及parse對對象進行轉化一般來說都是沒問題的,如果我們的對象中包含function類型的話 就會出現問題了。因爲stringify在轉化function時候就會返回丟失後的json串,那自然parse轉化後就會看不到我們的getName了。

貌似是通過這種方法進行深拷貝無法進行,其實不然。我們只需要解決JSON的“拷貝丟失”問題即可。

大家都會使用JSON的stringify以及parse方法,但是可能都沒有注意過這兩個方法不僅僅可以接受一個對象參數,拿stringify來看ECMAScript中明確表明使用方法JSON.stringify(value [, replacer] [, space])我們在平時都會用第一個value參數,

後兩個參數完全忽略掉。而根據介紹:

value是必須的(通常是一個對象或者數組);

第二個參數可選,表示一個函數或數組轉換結果。

如果第二個參數是一個函數,調用JSON.stringify函數,通過遍歷每個成員的鍵和值。鍵被保存,返回值將被使用,而不是原來的值。如果該函數返回未定義,成員被排除。返回就爲空。

第三個參數是處理返回空格用,在此不做過多講解。

我們看到第二個參數用法,第二個參數如果是函數的話 就會遍歷源對象中的鍵值,並且處理鍵值結果。這樣一來我們就可以在遍歷中判斷源對象中的某個值是否是function然後手動將其轉化爲字符串來達到我們轉化結果;

同理在parse方法中也有第二個參數對源對象進行遍歷操作方法。跟stringify使用類似,只是我們判斷的不是function類型(字符串無法判斷)而是判斷值中是否有function字段。那麼根據這規則我們實現改進後的深拷貝代碼如下:

function deepCopy(json){

    return

    JSON.parse(JSON.stringify(json,function(key,val){

            if(typeof val=='function'){

                return val+''//手動將其字符串化

            }

            return val;

        }

    ),function(key,val){

        if(val.indexOf&&val.indexOf('function')>-1){

            return eval("(function(){return "+val+" })()")

        }

        return val;

    });

}

這樣一來我們就可以得到我們想要的真正深拷貝結果了,也就解決了‘拷貝丟失’問題。

var newPerson=deepCopy(person);

newPerson.name='xiaopu'

console.log(person);//Object {name: "yuchao", age: "26",getName:function(){alert(this.name)}}

console.log(newPerson);//Object {name: "xiaopu", age: "26",getName:function(){alert(this.name)}}

利用這種方法可以對所有對象進行深拷貝包括function類型以及array類型。

不過JSON方法本身就是個問題,因爲在ie8標準模式以下版本都沒有這個對象,如果我們非得想用這個方法進行深度克隆 大可以從網上下個支持json的js文件或者自己寫一套支持json的js代碼也是可行的。

2.深度遍歷拷貝

我們來看看另外一種深度克隆方式,這是目前盛傳的一種方式,來自網上代碼。

function clone(obj){

    var objClone;

    if (obj.constructor == Object){

        objClone = new obj.constructor();

        }else{

            objClone = new obj.constructor(obj.valueOf());

        }

    for(var key in obj){

        if ( objClone[key] != obj[key] ){

            if ( typeof(obj[key]) == "object"){

                objClone[key] = clone(obj[key]);

            }else{

                objClone[key] = obj[key];

            }

        }

    }

    objClone.toString = obj.toString;

    objClone.valueOf = obj.valueOf;

    return objClone;

}

這段代碼簡單易懂而功能卻十分強大,我們來分析一段這段代碼:

這段代碼其實解決了兩個層面上的深度拷貝1是引用類型層次的拷貝,2是深度循環拷貝。當然也不存在對function處理問題。利用json拷貝也不會存在第二個層面問題。哈哈 半斤八兩。。

這個方法首先定義一個objClone變量,然後判斷傳入的obj的構造函數是什麼類型。如果是Object則直接新建一個空的Object對象,如果不是則將obj裏面的值賦予新建的obj空的構造函數(這樣返回的就是一個string對象或者數組或者function等對象)。

舉例來說obj="23";objClone則等於{0:'2',1:'3'};如果obj=[1,2];objClone則等於[[1,2]].

我們對obj進行循環,我們假設obj=[1,{name:'yuchao'}];那麼objClone就是[[1,{name:'yuchao'}]];objClone只有一個key那就是0,

這個時候obj會循環兩次即obj[0]=1,obj[1]={name:'yuchao'},objClone[key]只會循環一次,這個時候objClone[0]=obj=[1,{name:'yuchao'}],

objClone[1]時候就是undefined,無論key值是多少都符合判斷條件繼續向下執行,obj[0]值是一個數字就會走else條件即objClone[0]重新賦值爲1,

obj[1]是一個對象object則會通過typeof驗證深度拷貝(這個深度是拷貝層次的深度)這個時候就會將obj的值改爲{name:'yuchao'}重新走一遍clone方法。

在重新走之後 返回的值爲{name:'yuchao'}賦值給objClone[1],至此objClone=[1,{name:'yuchao'}];最後由於toString以及valueOf方法是內置對象,無法通過for in循環進行賦值,所以只能手動進行賦值。

==================================================================================================================================

ok,兩種模式進行深度克隆就說到這,總結一下:對於拷貝來說 兩種方法都沒有問題,

方法一優點是克隆速度快,不會消耗太多性能,缺點是瀏覽器兼容問題。

方法二優點是瀏覽器兼容,也是當今比較流行的克隆方式,缺點就是方法一的優點(因爲遍歷所以會消耗一些性能);

話說到這兒,選擇什麼 看你態度吧!!

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