js深復(fù)制和淺復(fù)制

javaScript的變量類(lèi)型

  • 基本類(lèi)型:
    5種基本數(shù)據(jù)類(lèi)型UndefinedNull、Boolean、NumberString,變量是直接按值存放的,存放在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,可以直接訪問(wèn)。

  • 引用類(lèi)型:
    存放在堆內(nèi)存中的對(duì)象,變量保存的是一個(gè)指針,這個(gè)指針指向另一個(gè)位置。當(dāng)需要訪問(wèn)引用類(lèi)型(如對(duì)象,數(shù)組等)的值時(shí),首先從棧中獲得該對(duì)象的地址指針,然后再?gòu)亩褍?nèi)存中取得所需的數(shù)據(jù)。

JavaScript存儲(chǔ)對(duì)象都是存地址的,所以淺拷貝會(huì)導(dǎo)致obj1obj2 指向同一塊內(nèi)存地址。改變了其中一方的內(nèi)容,都是在原來(lái)的內(nèi)存上做修改會(huì)導(dǎo)致拷貝對(duì)象和源對(duì)象都發(fā)生改變;而深拷貝是開(kāi)辟一塊新的內(nèi)存地址,將原對(duì)象的各個(gè)屬性逐個(gè)復(fù)制進(jìn)去。對(duì)拷貝對(duì)象和源對(duì)象各自的操作互不影響。

淺復(fù)制的實(shí)現(xiàn)

  • 簡(jiǎn)單的引用復(fù)制
   var data = {a: 1, b: 2};
   var c = data;
  • Object.assign()
    只會(huì)對(duì)第一層變量實(shí)現(xiàn)深復(fù)制;
    var data =  {
      a: 1,
      b: { f: { g: 1 } },
      c: [ 1, 2, 3 ]
    };
    
    var objData = Object.assign({}, data);
    
    console.log(objData === data); //false
    console.log(objData.b.f === data.b.f); //true
    
  • Array的slice和concat方法(與Object.assign()方法是一樣的)

    Arraysliceconcat方法不修改原數(shù)組,只會(huì)返回一個(gè)淺復(fù)制了原數(shù)組中的元素的一個(gè)新數(shù)組。它看起來(lái)像是深拷貝,而實(shí)際上它是淺拷貝。
    例子如下:

    var array = [1,2,3]; 
    var array_shallow = array; 
    var array_concat = array.concat(); 
    var array_slice = array.slice(0); 
    console.log(array === array_shallow); //true 
    console.log(array === array_slice); //false,“看起來(lái)”像深拷貝
    console.log(array === array_concat); //false,“看起來(lái)”像深拷貝
    

    可以看出,concatslice返回的不同的數(shù)組實(shí)例,這與直接的引用復(fù)制是不同的。

原數(shù)組的元素會(huì)按照下述規(guī)則拷貝:

  • 如果該元素是個(gè)對(duì)象引用 (不是實(shí)際的對(duì)象),slice 會(huì)拷貝這個(gè)對(duì)象引用到新的數(shù)組里。兩個(gè)對(duì)象引用都引用了同一個(gè)對(duì)象。如果被引用的對(duì)象發(fā)生改變,則新的和原來(lái)的數(shù)組中的這個(gè)元素也會(huì)發(fā)生改變。

     var array = [1, [1,2,3], {name:"array"}]; 
     var array_concat = array.concat();
     var array_slice = array.slice(0);
    
     array_concat[1][0] = 5;  //改變array_concat中數(shù)組元素的值 
    
     console.log(array[1]); //[5,2,3] 
     console.log(array_slice[1]); //[5,2,3] 
    
     array_slice[2].name = "array_slice"; //改變array_slice中對(duì)象元素的值 
    
     console.log(array[2].name); //array_slice
     console.log(array_concat[2].name); //array_slice
    

    可以看出Array的concatslice并不是真正的深復(fù)制,數(shù)組中的對(duì)象元素(Object,Array等)只是復(fù)制了引用。

  • 對(duì)于字符串、數(shù)字及布爾值來(lái)說(shuō)(不是 String、Number 或者 Boolean 對(duì)象),slice 會(huì)拷貝這些值到新的數(shù)組里。在別的數(shù)組里修改這些字符串或數(shù)字或是布爾值,將不會(huì)影響另一個(gè)數(shù)組。

  • 如果向兩個(gè)數(shù)組任一中添加了新元素,則另一個(gè)不會(huì)受到影響。

    var array = [1, 2, 3];
    
    var array_shallow = array;
    var array_concat = array.concat();
    var array_slice = array.slice(0);
    
    array.push(4);
    
    console.log(array_concat); //[1, 2, 3]
    

深復(fù)制的實(shí)現(xiàn)

JSON對(duì)象的parse和stringify

JSON對(duì)象是ES5中引入的新的類(lèi)型(支持的瀏覽器為IE8+),JSON對(duì)象parse方法可以將JSON字符串反序列化成JS對(duì)象,stringify方法可以將JS對(duì)象序列化成JSON字符串,借助這兩個(gè)方法,也可以實(shí)現(xiàn)對(duì)象的深拷貝。

var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target";  //改變target的name屬性

console.log(source.name); //source 
console.log(target.name); //target

target.child.name = "target child"; //改變target的child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));

console.log(target.name); //undefined
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));

console.log(target.name); //undefined
console.log(target.child); //Object {}

這種方法使用較為簡(jiǎn)單,可以滿(mǎn)足基本的深拷貝需求,而且能夠處理JSON格式能表示的所有數(shù)據(jù)類(lèi)型,但是對(duì)于正則表達(dá)式類(lèi)型、函數(shù)類(lèi)型等無(wú)法進(jìn)行深拷貝(而且會(huì)直接丟失相應(yīng)的值)。

還有一點(diǎn)不好的地方是它會(huì)拋棄對(duì)象的constructor。也就是深拷貝之后,不管這個(gè)對(duì)象原來(lái)的構(gòu)造函數(shù)是什么,在深拷貝之后都會(huì)變成Object。

如果對(duì)象中存在循環(huán)引用的情況也無(wú)法正確處理。

自定義方法實(shí)現(xiàn)深拷貝

function clone(data) {
    if (Array.isArray(data)) {
        let newArr = [];
        for (let i = 0; i < data.length; i++) {
            newArr[i] = clone(data[i]);
        }

        return newArr;
    } else if (data instanceof Object) {
        let obj = {};
        for (let key in data) {
            obj[key] = clone(data[[key]]);
        }
        return obj;
    }else {
        return data;
    }
}

var data =  {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var objData = clone(data);
console.log(objData === data);  //false
console.log(objdata.b.f === data.b.f); //false
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • js簡(jiǎn)介 Js是一種基于事件和對(duì)象驅(qū)動(dòng)的解釋性、松散性的語(yǔ)言。 一切皆對(duì)象 javascript 布蘭登艾奇 ...
    塔庫(kù)納瑪哈哈閱讀 1,346評(píng)論 0 2
  • underscore 的源碼中,有很多地方用到了 Array.prototype.slice() 方法,但是并沒(méi)有...
    theCoder閱讀 675評(píng)論 0 1
  • 單例模式 適用場(chǎng)景:可能會(huì)在場(chǎng)景中使用到對(duì)象,但只有一個(gè)實(shí)例,加載時(shí)并不主動(dòng)創(chuàng)建,需要時(shí)才創(chuàng)建 最常見(jiàn)的單例模式,...
    Obeing閱讀 2,311評(píng)論 1 10
  • 國(guó)慶節(jié)當(dāng)天我背起帆布包去往寧波,看望剛在那邊安家的發(fā)小,從小愛(ài)喚她“橘子”。 我是一個(gè)熱愛(ài)四處游走且懶的人,促使我...
    z大雁閱讀 342評(píng)論 0 1
  • React Native 官方文檔 React Native 開(kāi)源社區(qū) 【環(huán)境、組件、入門(mén)、進(jìn)價(jià)等】 JavaSc...
    小黑Swift閱讀 343評(píng)論 0 2

友情鏈接更多精彩內(nèi)容