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

          你最少用幾行代碼實(shí)現(xiàn)深拷貝?

          共 6817字,需瀏覽 14分鐘

           ·

          2022-04-16 02:36

          前言

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

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

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

          閱讀全文你將學(xué)習(xí)到:

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

          問題分析

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

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

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

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

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

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

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

          所以,我們要深度克隆,就需要對數(shù)據(jù)進(jìn)行遍歷并根據(jù)類型采取相應(yīng)的克隆方式。當(dāng)然因?yàn)閿?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;
          }
          復(fù)制代碼

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

          我們先進(jìn)行最基本的優(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;
          }
          復(fù)制代碼

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

          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);
          復(fù)制代碼

          執(zhí)行結(jié)果:

          image.png

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

          答案當(dāng)然是肯定的。

          //?判斷類型的方法移到外部,避免遞歸過程中多次執(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;
          }
          復(fù)制代碼

          執(zhí)行結(jié)果如下:

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

          具體解釋和內(nèi)容見MDN[1]

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

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

          我在我上面代碼的基礎(chǔ)上進(jìn)行了改造,改造后的代碼如下:

          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;
          }
          復(fù)制代碼

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

          最后,還有循環(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;
          }
          復(fù)制代碼

          總結(jié)

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

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

          關(guān)于本文

          作者:前端superman

          https://juejin.cn/post/7075351322014253064

          最后


          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群!領(lǐng)取最新最熱的前端算法小書、面試小書以及海量簡歷模板,期待與你共進(jìn)步!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          ?》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 39
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  国产A级黄色片 | 学生妹妹毛片 | 成人黄性视频 | 色哟哟哟 入口国产精品 | 精品视频国产 |