<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                # 實現深克隆 點擊關注本[公眾號](http://www.hmoore.net/book/dsh225/javascript_vue_css/edit#_118)獲取文檔最新更新,并可以領取配套于本指南的《**前端面試手冊**》以及**最標準的簡歷模板**. [TOC] ## 前言 實現一個深克隆是面試中常見的問題的,可是絕大多數面試者的答案都是不完整的,甚至是錯誤的,這個時候面試官會不斷追問,看看你到底理解不理解深克隆的原理,很多情況下一些一知半解的面試者就原形畢漏了. 我們就來看一下如何實現一個深克隆,當然面試中沒有讓你完整實現的時候,但是你一定要搞清楚其中的坑在哪里,才可以輕松應對面試官的追問. * JavaScript原始類型: Undefined、Null、Boolean、Number、String、Symbol * JavaScript引用類型:Object ## 淺克隆 **淺克隆**之所以被稱為**淺克隆**,是因為對象只會被克隆最外部的一層,至于更深層的對象,則依然是通過引用指向同一塊堆內存. ~~~ // 淺克隆函數 function shallowClone(o) { const obj = {}; for ( let i in o) { obj[i] = o[i]; } return obj; } // 被克隆對象 const oldObj = { a: 1, b: [ 'e', 'f', 'g' ], c: { h: { i: 2 } } }; const newObj = shallowClone(oldObj); console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 } console.log(oldObj.c.h === newObj.c.h); // true ~~~ 我們可以看到,很明顯雖然`oldObj.c.h`被克隆了,但是它還與`oldObj.c.h`相等,這表明他們依然指向同一段堆內存,這就造成了如果對`newObj.c.h`進行修改,也會影響`oldObj.c.h`,這就不是一版好的克隆. ~~~ newObj.c.h.i = 'change'; console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 'change' } ~~~ 我們改變了`newObj.c.h.i`的值,`oldObj.c.h.i`也被改變了,這就是淺克隆的問題所在. 當然有一個新的api`Object.assign()`也可以實現淺復制,但是效果跟上面沒有差別,所以我們不再細說了. ## 深克隆 ### JSON.parse方法 前幾年微博上流傳著一個傳說中最便捷實現深克隆的方法, JSON對象parse方法可以將JSON字符串反序列化成JS對象,stringify方法可以將JS對象序列化成JSON字符串,這兩個方法結合起來就能產生一個便捷的深克隆. ~~~ const newObj = JSON.parse(JSON.stringify(oldObj)); ~~~ 我們依然用上一節的例子進行測試 ~~~ const oldObj = { a: 1, b: [ 'e', 'f', 'g' ], c: { h: { i: 2 } } }; const newObj = JSON.parse(JSON.stringify(oldObj)); console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 } console.log(oldObj.c.h === newObj.c.h); // false newObj.c.h.i = 'change'; console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 2 } ~~~ 果然,這是一個實現深克隆的好方法,但是這個解決辦法是不是太過簡單了. 確實,這個方法雖然可以解決絕大部分是使用場景,但是卻有很多坑. 1. 他無法實現對函數 、RegExp等特殊對象的克隆 2. 會拋棄對象的constructor,所有的構造函數會指向Object 3. 對象有循環引用,會報錯 主要的坑就是以上幾點,我們一一測試下: ~~~ // 構造函數 function person(pname) { this.name = pname; } const Messi = new person('Messi'); // 函數 function say() { console.log('hi'); }; const oldObj = { a: say, b: new Array(1), c: new RegExp('ab+c', 'i'), d: Messi }; const newObj = JSON.parse(JSON.stringify(oldObj)); // 無法復制函數 console.log(newObj.a, oldObj.a); // undefined [Function: say] // 稀疏數組復制錯誤 console.log(newObj.b[0], oldObj.b[0]); // null undefined // 無法復制正則對象 console.log(newObj.c, oldObj.c); // {} /ab+c/i // 構造函數指向錯誤 console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person] ~~~ 我們可以看到在對函數、正則對象、稀疏數組等對象克隆時會發生意外,構造函數指向也會發生錯誤。 ~~~ const oldObj = {}; oldObj.a = oldObj; const newObj = JSON.parse(JSON.stringify(oldObj)); console.log(newObj.a, oldObj.a); // TypeError: Converting circular structure to JSON ~~~ 對象的循環引用會拋出錯誤. ### 2.2 構造一個深克隆函數 我們知道要想實現一個靠譜的深克隆方法,上一節提到的**序列/反序列**是不可能了,而通常教程里提到的方法也是不靠譜的,他們存在的問題跟上一屆序列反序列操作中凸顯的問題是一致的.![](https://user-gold-cdn.xitu.io/2018/3/28/1626bc7a5caf947c?w=555&h=298&f=png&s=64444)*(這個方法也會出現上一節提到的問題)* 由于要面對不同的對象(正則、數組、Date等)要采用不同的處理方式,我們需要實現一個對象類型判斷函數。 ~~~ const isType = (obj, type) => { if (typeof obj !== 'object') return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case 'Array': flag = typeString === '[object Array]'; break; case 'Date': flag = typeString === '[object Date]'; break; case 'RegExp': flag = typeString === '[object RegExp]'; break; default: flag = false; } return flag; }; ~~~ 這樣我們就可以對特殊對象進行類型判斷了,從而采用針對性的克隆策略. ~~~ const arr = Array.of(3, 4, 5, 2); console.log(isType(arr, 'Array')); // true ~~~ 對于正則對象,我們在處理之前要先補充一點新知識. 我們需要通過[正則的擴展](http://es6.ruanyifeng.com/#docs/regex#flags-%E5%B1%9E%E6%80%A7)了解到`flags`屬性等等,因此我們需要實現一個提取flags的函數. ~~~ const getRegExp = re => { var flags = ''; if (re.global) flags += 'g'; if (re.ignoreCase) flags += 'i'; if (re.multiline) flags += 'm'; return flags; }; ~~~ 做好了這些準備工作,我們就可以進行深克隆的實現了. ~~~ /** * deep clone * @param {[type]} parent object 需要進行克隆的對象 * @return {[type]} 深克隆后的對象 */ const clone = parent => { // 維護兩個儲存循環引用的數組 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== 'object') return parent; let child, proto; if (isType(parent, 'Array')) { // 對數組做特殊處理 child = []; } else if (isType(parent, 'RegExp')) { // 對正則對象做特殊處理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, 'Date')) { // 對Date對象做特殊處理 child = new Date(parent.getTime()); } else { // 處理對象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切斷原型鏈 child = Object.create(proto); } // 處理循環引用 const index = parents.indexOf(parent); if (index != -1) { // 如果父數組存在本對象,說明之前已經被引用過,直接返回此對象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 遞歸 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); }; ~~~ 我們做一下測試 ~~~ function person(pname) { this.name = pname; } const Messi = new person('Messi'); function say() { console.log('hi'); } const oldObj = { a: say, c: new RegExp('ab+c', 'i'), d: Messi, }; oldObj.b = oldObj; const newObj = clone(oldObj); console.log(newObj.a, oldObj.a); // [Function: say] [Function: say] console.log(newObj.b, oldObj.b); // { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person] ~~~ 當然,我們這個深克隆還不算完美,例如Buffer對象、Promise、Set、Map可能都需要我們做特殊處理,另外對于確保沒有循環引用的對象,我們可以省去對循環引用的特殊處理,因為這很消耗時間,不過一個基本的深克隆函數我們已經實現了。 * * * ## 總結 實現一個完整的深克隆是由許多坑要踩的,npm上一些庫的實現也不夠完整,在生產環境中最好用`lodash`的深克隆實現. 在面試過程中,我們上面提到的眾多坑是面試官很可能追問你的,要知道坑在哪里,能答出來才是你的加分項,在面試過程中必須要有一兩個閃光點,如果只知道**序列/反序列**這種投機取巧的方法,在追問下不僅拿不到分,很可能造成只懂個皮毛的印象,畢竟,面試面得就是你知識的深度. * * * ## 公眾號 想要實時關注筆者最新的文章和最新的文檔更新請關注公眾號**程序員面試官**,后續的文章會優先在公眾號更新. **簡歷模板**:關注公眾號回復「模板」獲取 **《前端面試手冊》**:配套于本指南的突擊手冊,關注公眾號回復「fed」獲取 ![2019-08-12-03-18-41](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/d846f65d5025c4b6c4619662a0669503.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看