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

          基礎鞏固-你最少用幾行代碼實現(xiàn)深拷貝?

          共 6806字,需瀏覽 14分鐘

           ·

          2022-06-02 14:01

          點擊上方?前端Q,關注公眾號

          回復加群,加入前端Q技術交流群


          前言

          深度克?。ㄉ羁截悾┮恢倍际浅?、中級前端面試中經(jīng)常被問到的題目,網(wǎng)上介紹的實現(xiàn)方式也都各有千秋,大體可以概括為三種方式:

          1. JSON.stringify+JSON.parse, 這個很好理解;
          2. 全量判斷類型,根據(jù)類型做不同的處理
          3. 2的變型,簡化類型判斷過程

          前兩種比較常見也比較基礎,所以我們今天主要討論的是第三種。

          閱讀全文你將學習到:

          1. 更簡潔的深度克隆方式
          2. Object.getOwnPropertyDescriptors()api
          3. 類型判斷的通用方法

          問題分析

          深拷貝 自然是 相對 淺拷貝 而言的。我們都知道 引用數(shù)據(jù)類型 變量存儲的是數(shù)據(jù)的引用,就是一個指向內存空間的指針, 所以如果我們像賦值簡單數(shù)據(jù)類型那樣的方式賦值的話,其實只能復制一個指針引用,并沒有實現(xiàn)真正的數(shù)據(jù)克隆。

          通過這個例子很容易就能理解:

          const?obj1?=?{
          ????name:?'superman'
          }
          const?obj2?=?obj1;
          obj1.name?=?'前端切圖仔';
          console.log(obj2.name);?//?前端切圖仔
          復制代碼

          所以深度克隆就是為了解決引用數(shù)據(jù)類型不能被通過賦值的方式 復制 的問題。

          引用數(shù)據(jù)類型

          我們不妨來羅列一下引用數(shù)據(jù)類型都有哪些:

          • ES6之前:Object, Array, Date, RegExp, Error,
          • ES6之后:Map, Set, WeakMap, WeakSet,

          所以,我們要深度克隆,就需要對數(shù)據(jù)進行遍歷并根據(jù)類型采取相應的克隆方式。當然因為數(shù)據(jù)會存在多層嵌套的情況,采用遞歸是不錯的選擇。

          簡單粗暴版本

          function?deepClone(obj)?{
          ????let?res?=?{};
          ????//?類型判斷的通用方法
          ????function?getType(obj)?{
          ????????return?Object.prototype.toString.call(obj).replaceAll(new?RegExp(/\[|\]|object?/g),?"");
          ????}
          ????const?type?=?getType(obj);
          ????const?reference?=?["Set",?"WeakSet",?"Map",?"WeakMap",?"RegExp",?"Date",?"Error"];
          ????if?(type?===?"Object")?{
          ????????for?(const?key?in?obj)?{
          ????????????if?(Object.hasOwnProperty.call(obj,?key))?{
          ????????????????res[key]?=?deepClone(obj[key]);
          ????????????}
          ????????}
          ????}?else?if?(type?===?"Array")?{
          ????????console.log('array?obj',?obj);
          ????????obj.forEach((e,?i)?=>?{
          ????????????res[i]?=?deepClone(e);
          ????????});
          ????}
          ????else?if?(type?===?"Date")?{
          ????????res?=?new?Date(obj);
          ????}?else?if?(type?===?"RegExp")?{
          ????????res?=?new?RegExp(obj);
          ????}?else?if?(type?===?"Map")?{
          ????????res?=?new?Map(obj);
          ????}?else?if?(type?===?"Set")?{
          ????????res?=?new?Set(obj);
          ????}?else?if?(type?===?"WeakMap")?{
          ????????res?=?new?WeakMap(obj);
          ????}?else?if?(type?===?"WeakSet")?{
          ????????res?=?new?WeakSet(obj);
          ????}else?if?(type?===?"Error")?{
          ????????res?=?new?Error(obj);
          ????}
          ?????else?{
          ????????res?=?obj;
          ????}
          ????return?res;
          }
          復制代碼

          其實這就是我們最前面提到的第二種方式,很傻對不對,明眼人一眼就能看出來有很多冗余代碼可以合并。

          我們先進行最基本的優(yōu)化:

          合并冗余代碼

          將一眼就能看出來冗余的代碼合并下。

          function?deepClone(obj)?{
          ????let?res?=?null;
          ????//?類型判斷的通用方法
          ????function?getType(obj)?{
          ????????return?Object.prototype.toString.call(obj).replaceAll(new?RegExp(/\[|\]|object?/g),?"");
          ????}
          ????const?type?=?getType(obj);
          ????const?reference?=?["Set",?"WeakSet",?"Map",?"WeakMap",?"RegExp",?"Date",?"Error"];
          ????if?(type?===?"Object")?{
          ????????res?=?{};
          ????????for?(const?key?in?obj)?{
          ????????????if?(Object.hasOwnProperty.call(obj,?key))?{
          ????????????????res[key]?=?deepClone(obj[key]);
          ????????????}
          ????????}
          ????}?else?if?(type?===?"Array")?{
          ????????console.log('array?obj',?obj);
          ????????res?=?[];
          ????????obj.forEach((e,?i)?=>?{
          ????????????res[i]?=?deepClone(e);
          ????????});
          ????}
          ????//?優(yōu)化此部分冗余判斷
          ????//?else?if?(type?===?"Date")?{
          ????//?????res?=?new?Date(obj);
          ????//?}?else?if?(type?===?"RegExp")?{
          ????//?????res?=?new?RegExp(obj);
          ????//?}?else?if?(type?===?"Map")?{
          ????//?????res?=?new?Map(obj);
          ????//?}?else?if?(type?===?"Set")?{
          ????//?????res?=?new?Set(obj);
          ????//?}?else?if?(type?===?"WeakMap")?{
          ????//?????res?=?new?WeakMap(obj);
          ????//?}?else?if?(type?===?"WeakSet")?{
          ????//?????res?=?new?WeakSet(obj);
          ????//?}else?if?(type?===?"Error")?{
          ????//???res?=?new?Error(obj);
          ????//}
          ????else?if?(reference.includes(type))?{
          ????????res?=?new?obj.constructor(obj);
          ????}?else?{
          ????????res?=?obj;
          ????}
          ????return?res;
          }
          復制代碼

          為了驗證代碼的正確性,我們用下面這個數(shù)據(jù)驗證下:

          const?map?=?new?Map();
          map.set("key",?"value");
          map.set("ConardLi",?"coder");

          const?set?=?new?Set();
          set.add("ConardLi");
          set.add("coder");

          const?target?=?{
          ????field1:?1,
          ????field2:?undefined,
          ????field3:?{
          ????????child:?"child",
          ????},
          ????field4:?[2,?4,?8],
          ????empty:?null,
          ????map,
          ????set,
          ????bool:?new?Boolean(true),
          ????num:?new?Number(2),
          ????str:?new?String(2),
          ????symbol:?Object(Symbol(1)),
          ????date:?new?Date(),
          ????reg:?/\d+/,
          ????error:?new?Error(),
          ????func1:?()?=>?{
          ????????let?t?=?0;
          ????????console.log("coder",?t++);
          ????},
          ????func2:?function?(a,?b)?{
          ????????return?a?+?b;
          ????},
          };
          //測試代碼
          const?test1?=?deepClone(target);
          target.field4.push(9);
          console.log('test1:?',?test1);
          復制代碼

          執(zhí)行結果:

          image.png

          還有進一步優(yōu)化的空間嗎?

          答案當然是肯定的。

          //?判斷類型的方法移到外部,避免遞歸過程中多次執(zhí)行
          const?judgeType?=?origin?=>?{
          ????return?Object.prototype.toString.call(origin).replaceAll(new?RegExp(/\[|\]|object?/g),?"");
          };
          const?reference?=?["Set",?"WeakSet",?"Map",?"WeakMap",?"RegExp",?"Date",?"Error"];
          function?deepClone(obj)?{
          ????//?定義新的對象,最后返回
          ?????//通過?obj?的原型創(chuàng)建對象
          ????const?cloneObj?=?Object.create(Object.getPrototypeOf(obj),?Object.getOwnPropertyDescriptors(obj));

          ????//?遍歷對象,克隆屬性
          ????for?(let?key?of?Reflect.ownKeys(obj))?{
          ????????const?val?=?obj[key];
          ????????const?type?=?judgeType(val);
          ????????if?(reference.includes(type))?{
          ????????????newObj[key]?=?new?val.constructor(val);
          ????????}?else?if?(typeof?val?===?"object"?&&?val?!==?null)?{
          ????????????//?遞歸克隆
          ????????????newObj[key]?=?deepClone(val);
          ????????}?else?{
          ????????????//?基本數(shù)據(jù)類型和function
          ????????????newObj[key]?=?val;
          ????????}
          ????}
          ????return?newObj;
          }
          復制代碼

          執(zhí)行結果如下:

          image.png
          • Object.getOwnPropertyDescriptors() ?方法用來獲取一個對象的所有自身屬性的描述符。
          • 返回所指定對象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對象。

          具體解釋和內容見MDN[1]

          這樣做的好處就是能夠提前定義好最后返回的數(shù)據(jù)類型。

          這個實現(xiàn)參考了網(wǎng)上一位大佬的實現(xiàn)方式,個人覺得理解成本有點高,而且對數(shù)組類型的處理也不是特別優(yōu)雅, 返回類數(shù)組。

          我在我上面代碼的基礎上進行了改造,改造后的代碼如下:

          function?deepClone(obj)?{
          ????let?res?=?null;
          ????const?reference?=?[Date,?RegExp,?Set,?WeakSet,?Map,?WeakMap,?Error];
          ????if?(reference.includes(obj?.constructor))?{
          ????????res?=?new?obj.constructor(obj);
          ????}?else?if?(Array.isArray(obj))?{
          ????????res?=?[];
          ????????obj.forEach((e,?i)?=>?{
          ????????????res[i]?=?deepClone(e);
          ????????});
          ????}?else?if?(typeof?obj?===?"object"?&&?obj?!==?null)?{
          ????????res?=?{};
          ????????for?(const?key?in?obj)?{
          ????????????if?(Object.hasOwnProperty.call(obj,?key))?{
          ????????????????res[key]?=?deepClone(obj[key]);
          ????????????}
          ????????}
          ????}?else?{
          ????????res?=?obj;
          ????}
          ????return?res;
          }
          復制代碼

          雖然代碼量上沒有什么優(yōu)勢,但是整體的理解成本和你清晰度上我覺得會更好一點。那么你覺得呢?

          最后,還有循環(huán)引用問題,避免出現(xiàn)無線循環(huán)的問題。

          我們用hash來存儲已經(jīng)加載過的對象,如果已經(jīng)存在的對象,就直接返回。

          function?deepClone(obj,?hash?=?new?WeakMap())?{
          ????if?(hash.has(obj))?{
          ????????return?obj;
          ????}
          ????let?res?=?null;
          ????const?reference?=?[Date,?RegExp,?Set,?WeakSet,?Map,?WeakMap,?Error];

          ????if?(reference.includes(obj?.constructor))?{
          ????????res?=?new?obj.constructor(obj);
          ????}?else?if?(Array.isArray(obj))?{
          ????????res?=?[];
          ????????obj.forEach((e,?i)?=>?{
          ????????????res[i]?=?deepClone(e);
          ????????});
          ????}?else?if?(typeof?obj?===?"object"?&&?obj?!==?null)?{
          ????????res?=?{};
          ????????for?(const?key?in?obj)?{
          ????????????if?(Object.hasOwnProperty.call(obj,?key))?{
          ????????????????res[key]?=?deepClone(obj[key]);
          ????????????}
          ????????}
          ????????hash.set(obj,?res);
          ????}?else?{
          ????????res?=?obj;
          ????}
          ????return?res;
          }
          復制代碼

          總結

          對于深拷貝的實現(xiàn),可能存在很多不同的實現(xiàn)方式,關鍵在于理解其原理,并能夠記住一種最容易理解和實現(xiàn)的方式,面對類似的問題才能做到 臨危不亂,泰然自若。上面的實現(xiàn)你覺得哪個更好呢?歡迎大佬們在評論區(qū)交流~

          更文不易, 看完記得點個贊支持一下哦~ 這將是我寫作的動力源泉~

          關于本文

          作者:前端superman

          https://juejin.cn/post/7075351322014253064


          往期推薦


          深入了解前端路由 hash 與 history 差異
          一位 Google 工程師的十年總結
          手把手 從 0 到 1 搞定官網(wǎng)開發(fā)

          最后


          • 歡迎加我微信,拉你進技術群,長期交流學習...

          • 歡迎關注「前端Q」,認真學前端,做個專業(yè)的技術人...

          點個在看支持我吧
          瀏覽 22
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  麻豆操逼爽 | 日本一区视频免费 | 色中色俺来也 | 大鸡巴成人性爱在线视频 | 国产亚洲日韩视频 |