<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          對象深淺拷貝與WeakMap

          共 6316字,需瀏覽 13分鐘

           ·

          2020-09-20 23:33

          作者:JS-Even-JS
          來源:SegmentFault 思否社區(qū)



          一、淺拷貝


          當(dāng)我們進行數(shù)據(jù)拷貝的時候,如果該數(shù)據(jù)是一個引用類型,并且拷貝的時候僅僅傳遞的是該對象的指針,那么就屬于淺拷貝。由于拷貝過程中只傳遞了指針,并沒有重新創(chuàng)建一個新的引用類型對象,所以二者共享同一片內(nèi)存空間,即通過指針指向同一片內(nèi)存空間。


          常見的對象淺拷貝方式為:


          Object.assign()


          const?a?=?{msg:?{name:?"lihb"}};
          const?b?=?Object.assign({},?a);
          a.msg.name?=?"lily";
          console.log(b.msg.name);?//?lily


          一旦修改對象a的msg的name屬性值,克隆的b對象的msg的name屬性也跟著變化了,所以屬于淺拷貝。


          ② 擴展運算符(...)



          const?a?=?{msg:?{name:?"lihb"}};
          const?b?=?{...a};
          a.msg.name?=?"lily";
          console.log(b.msg.name);?//?lily


          同樣的,修改對象a中的name,克隆對象b中的name值也跟著變化了。

          常見的數(shù)組淺拷貝方式為:


          ① slice()


          const?a?=?[{name:?"lihb"}];
          const?b?=?a.slice();
          a[0].name?=?"lily";
          console.log(b[0].name);?//?lily


          一旦修改對象a[0]的name屬性值,克隆的對象b[0]的name屬性值也跟著變化,所以屬于淺拷貝。


          ② concat()


          const?a?=?[{name:?"lihb"}];
          const?b?=?a.concat();
          a[0].name?=?"lily";
          console.log(b[0].name);//?lily


          同樣的,修改對象a[0]的name屬性值,克隆的對象b[0]的name屬性值也跟著變化。


          ③ 擴展運算符(...)


          const?a?=?[{name:?"lihb"}];
          const?b?=?[...a];
          a[0].name?=?"lily";
          console.log(b[0].name);?//?lily


          同樣的,修改對象a[0]的name屬性值,克隆的對象b[0]的name屬性值也跟著變化。


          二、深拷貝


          當(dāng)我們進行數(shù)據(jù)拷貝的時候,如果該數(shù)據(jù)是一個引用類型,并且拷貝的時候,傳遞的不是該對象的指針,而是創(chuàng)建一個新的與之相同的引用類型數(shù)據(jù),那么就屬于深拷貝。由于拷貝過程中重新創(chuàng)建了一個新的引用類型數(shù)據(jù),所以二者擁有獨立的內(nèi)存空間,相互修改不會互相影響。


          常見的對象和數(shù)組深拷貝方式為:


          ① JSON.stringify()和JSON.parse()


          const?a?=?{msg:?{name:?"lihb"},?arr:?[1,?2,?3]};
          const?b?=?JSON.parse(JSON.stringify(a));
          a.msg.name?=?"lily";
          console.log(b.msg.name);?//?lihb
          a.arr.push(4);
          console.log(b.arr[4]);?//?undefined


          可以看到,對對象a進行修改后,拷貝的對象b中的數(shù)組和對象都沒有受到影響,所以屬于深拷貝。

          雖然JSON.stringify()和JSON.parse()能實現(xiàn)深拷貝,但是其并不能處理所有數(shù)據(jù)類型,當(dāng)數(shù)據(jù)為函數(shù)的時候,拷貝的結(jié)果為null;當(dāng)數(shù)據(jù)為正則的時候,拷貝結(jié)果為一個空對象{},如:


          const?a?=?{
          ????fn:?()?=>?{},
          ????reg:?new?RegExp(/123/)
          };
          const?b?=?JSON.parse(JSON.stringify(a));
          console.log(b);?//?{?reg:?{}?}


          可以看到,JSON.stringify()和JSON.parse()對正則和函數(shù)深拷貝無效。


          三、實現(xiàn)深拷貝


          進行深拷貝的時候,我們主要關(guān)注的是對象類型,即在拷貝對象的時候,該對象必須創(chuàng)建的一個新的對象,如果對象的屬性值仍然為對象,則需要進行遞歸拷貝。對象類型主要為,Date、RegExp、Array、Object等。


          function?deepClone(source)?{
          ????if?(typeof?source?!==?"object")?{?//?非對象類型(undefined、boolean、number、string、symbol),直接返回原值即可
          ????????return?source;
          ????}
          ????if?(source?===?null)?{?//?為null類型的時候
          ????????return?source;
          ????}
          ????if?(source?instanceof?Date)?{?//?Date類型
          ????????return?new?Date(source);
          ????}
          ????if?(source?instanceof?RegExp)?{?//?RegExp正則類型
          ????????return?new?RegExp(source);
          ????}
          ????let?result;
          ????if?(Array.isArray(source))?{?//?數(shù)組
          ????????result?=?[];
          ????????source.forEach((item)?=>?{
          ????????????result.push(deepClone(item));
          ????????});
          ????????return?result;
          ????}?else?{?//?為對象的時候
          ????????result?=?{};
          ????????const?keys?=?[...Object.getOwnPropertyNames(source),?...Object.getOwnPropertySymbols(source)];?//?取出對象的key以及symbol類型的key
          ????????keys.forEach(key?=>?{
          ????????????let?item?=?source[key];
          ????????????result[key]?=?deepClone(item);
          ????????});
          ????????return?result;
          ????}
          }
          let?a?=?{name:?"a",?msg:?{name:?"lihb"},?date:?new?Date("2020-09-17"),?reg:?new?RegExp(/123/)};
          let?b?=?deepClone(a);
          a.msg.name?=?"lily";
          a.date?=?new?Date("2020-08-08");
          a.reg?=?new?RegExp(/456/);
          console.log(b);
          //?{?name:?'a',?msg:?{?name:?'lihb'?},?date:?2020-09-17T00:00:00.000Z,?reg:?/123/?}


          由于需要進行遞歸拷貝,所以對于非對象類型的數(shù)據(jù)直接返回原值即可。對于Date類型的值,則直接傳入當(dāng)前值new一個Date對象即可,對于RegExp對象的值,也是直接傳入當(dāng)前值new一個RegExp對象即可。對于數(shù)組類型,遍歷數(shù)組的每一項并進行遞歸拷貝即可。對于對象,同樣遍歷對象的所有key值,同時對其值進行遞歸拷貝即可。對于對象還需要考慮屬性值為Symbol的類型,因為Symbol類型的key無法直接通過Object.keys()枚舉到。


          三、相互引用問題


          上面的深拷貝實現(xiàn)看上去很完善,但是還有一種情況未考慮到,那就是對象相互引用的情況,這種情況將會導(dǎo)致遞歸無法結(jié)束。


          const?a?=?{name:?"a"};
          const?b?=?{name:?"b"};
          a.b?=?b;
          b.a?=?a;?//?相互引用
          console.log(a);?//?{?name:?'a',?b:?{?name:?'b',?a:?[Circular]?}?}


          對于上面這種情況,我們需要怎么拷貝相互引用后的a對象呢?
          我們也是按照上面的方式進行遞歸拷貝:


          //?①?創(chuàng)建一個空的對象,表示對a對象的拷貝結(jié)果
          const?aClone?=?{};
          //?②?遍歷a中的屬性,name和b,?首先拷貝name屬性和b屬性
          aClone.name?=?a.name;
          //?③?接著拷貝b屬性,而b的屬性值為b對象,需要進行遞歸拷貝,同時包含name和a屬性,先拷貝name屬性
          const?bClone?=?{};
          bClone.name?=?b.name;
          //?④?接著拷貝a屬性,而a的屬性值為a對象,我們需要將之前a的拷貝對象aClone賦值即可
          bClone.a?=?aClone;
          //?⑤?此時bClone已經(jīng)拷貝完成,再將bClone賦值給aClone的b屬性即可
          aClone.b?=?bClone;
          console.log(aClone);?//?{?name:?'a',?b:?{?name:?'b',?a:?[Circular]?}}

          其中最關(guān)鍵的就是第④步,這里就是結(jié)束遞歸的關(guān)鍵,我們是拿到了a的拷貝結(jié)果進行了賦值,所以我們需要記錄下某個對象的拷貝結(jié)果,如果之前已經(jīng)拷貝過,那么我們直接拿到拷貝結(jié)果賦值即可完成相互引用。


          而JS提供了一種WeakMap數(shù)據(jù)結(jié)構(gòu),其只能用對象作為key值進行存儲,我們可以用拷貝前的對象作為key,拷貝后的結(jié)果對象作為value,當(dāng)出現(xiàn)相互引用關(guān)系的時候,我們只需要從WeakMap對象中取出之前已經(jīng)拷貝的結(jié)果對象賦值即可形成相互引用關(guān)系。


          function?deepClone(source,?map?=?new?WeakMap())?{?//?傳入一個WeakMap對象用于記錄拷貝前和拷貝后的映射關(guān)系
          ????if?(typeof?source?!==?"object")?{?//?非對象類型(undefined、boolean、number、string、symbol),直接返回原值即可
          ????????return?source;
          ????}
          ????if?(source?===?null)?{?//?為null類型的時候
          ????????return?source;
          ????}
          ????if?(source?instanceof?Date)?{?//?Date類型
          ????????return?new?Date(source);
          ????}
          ????if?(source?instanceof?RegExp)?{?//?RegExp正則類型
          ????????return?new?RegExp(source);
          ????}
          ????if?(map.get(source))?{?//?如果存在相互引用,則從map中取出之前拷貝的結(jié)果對象并返回以便形成相互引用關(guān)系
          ????????return?map.get(source);
          ????}
          ????let?result;
          ????if?(Array.isArray(source))?{?//?數(shù)組
          ????????result?=?[];
          ????????map.set(source,?result);?//?數(shù)組也會存在相互引用
          ????????source.forEach((item)?=>?{
          ????????????result.push(deepClone(item,?map));
          ????????});
          ????????return?result;
          ????}?else?{?//?為對象的時候
          ????????result?=?{};
          ????????map.set(source,?result);?//?保存已拷貝的對象
          ????????const?keys?=?[...Object.getOwnPropertyNames(source),?...Object.getOwnPropertySymbols(source)];?//?取出對象的key以及symbol類型的key
          ????????keys.forEach(key?=>?{
          ????????????let?item?=?source[key];
          ????????????result[key]?=?deepClone(item,?map);
          ????????});
          ????????return?result;
          ????}
          }


          至此已經(jīng)實現(xiàn)了一個相對比較完善的深拷貝。


          四、WeakMap(補充)


          WeakMap有一個特點就是屬性值只能是對象,而Map的屬性值則無限制,可以是任何類型。從其名字可以看出,WeakMap是一種弱引用,所以不會造成內(nèi)存泄漏。接下來我們就是要弄清楚為什么是弱引用。

          我們首先看看WeakMap的polyfill實現(xiàn),如下:


          var?WeakMap?=?function()?{
          ????this.name?=?'__wm__'?+?uuid();
          };
          WeakMap.prototype?=?{
          ????set:?function(key,?value)?{?//?這里的key是一個對象,并且是局部變量
          ????????Object.defineProperty(key,?this.name,?{?//?給傳入的對象上添加一個this.name屬性,值為要保存的結(jié)果
          ????????????value:?[key,?value],
          ????????});
          ????????return?this;
          ????},
          ????get:?function(key)?{
          ????????var?entry?=?key[this.name];
          ????????return?entry?&&?(entry[0]?===?key???entry[1]?:?undefined);
          ????}
          };


          從WeakMap的實現(xiàn)上我們可以看到,WeakMap并沒有直接引用傳入的對象,當(dāng)我們調(diào)用WeakMap對象set()方法的時候,會傳入一個對象,然后在傳入的對象上添加一個this.name屬性,值為一個數(shù)組,第一項為傳入的對象,第二項為設(shè)置的值,當(dāng)set方法調(diào)用結(jié)束后,局部變量key被釋放,所以WeakMap并沒有直接引用傳入的對象,即弱引用。


          其執(zhí)行過程等價于下面的方法調(diào)用:


          var?obj?=?{name:?"lihb"};

          function?set(key,?value)?{
          ????var?k?=?"this.name";?//?這里模擬this.name的值作為key
          ????key[k]?=?[key,?value];
          }
          set(obj,?"test");?//?這里模擬WeakMap的set()方法
          obj?=?null;?//?obj將會被垃圾回收器回收


          所以set的作用就是給傳入的對象設(shè)置了一個屬性而已,不存在被誰引用的關(guān)系





          點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。


          -?END -


          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  美女操逼免费网站 | 逼特逼 | 日本一级片直播 | 国产老熟女一区二区三区 | 国产精品一区二区三区高潮 |