你覺得面試官在問深拷貝的時候,僅僅是在問深拷貝嗎?

deep.001.jpeg

深拷貝能夠說是前端面試中很是高頻的問題,也是一道基礎題。所謂的基礎不是說深拷貝自己是一個很是簡單、很是基礎的問題,而是面試官要經過深拷貝來考察候選人的JavaScript基礎,甚至是程序設計能力。前端

爲何須要深拷貝?

第一個問題,也是最淺顯的問題,爲何 JavaScript 中須要深拷貝?或者說若是不使用深拷貝複製對象會帶來哪些問題?面試

咱們知道在 JavaScript 中存在「引用類型「和「值類型「的概念。由於「引用類型「的特殊性,致使咱們複製對象不能經過簡單的clone = target,因此須要把原對象的屬性值一一賦給新對象。數組

而對象的屬性其值也多是另外一個對象,因此咱們須要遞歸spa

如何獲取原對象的屬性?

經過for...in可以遍歷對象上的屬性;也能夠經過Object.keys(target)獲取到對象上的屬性數組後再進行遍歷。
這裏選用for...in由於相比Object.keys(target)它還會遍歷對象原型鏈上的屬性。prototype

ES6 Symbol 類型也能夠做爲對象的 key ,如何獲取它們?設計

如何判斷對象的類型?

可使用typeof判斷目標是否爲引用類型,這裏有一處須要注意:typeof null也是objectcode

function deepClone(target) {
    const targetType = typeof target;
    if (targetType === 'object' || targetType === 'function') {
        let clone = Array.isArray(target)?[]:{}
        for (const key in target) {
            clone[key] = deepClone(target[key])
        }
        return clone;
    }
    return target;
}

上述代碼就完成了一個很是基礎的深拷貝。可是對於引用類型的處理,它仍然是不完善的:對象

它無法處理Date或者正則這樣的對象。爲何?blog

「回字的四樣寫法「--具體類型的識別

獲取一個對象具體類型有哪些方式?教程

經常使用的方式有target.constructor.nameObject.prototype.toString.call(target)instanceOf

  • instacneOf能夠用來判斷對象類型,可是Date的實例同時也是Object的實例,此處用於判斷是不許確的;
  • target.constructor.name獲得的是構造器名稱,而構造器是能夠被修改的;
  • Object.prototype.toString.call(target)返回的是類名,而在ES5中只有內置類型對象纔有類名。

因此此處咱們最合適的選擇是Object.prototype.toString.call(target)

Object.prototype.toString.call(target)也存在一些問題,你知道嗎?

稍微改進一下代碼,作一些簡單的類型判斷:

function deepClone(target) {
    const targetType = typeof target;
    if (targetType === 'object' || targetType === 'function') {
        let clone = Array.isArray(target)?[]:{};

        if(Object.prototype.toString.call(target) === '[object Date]'){
            clone = new Date(target)
        }
        
        if(Object.prototype.toString.call(target) === '[object Object]'
        ||Object.prototype.toString.call(target) === '[object Array]'){
            for (const key in target) {
                clone[key] = deepClone(target[key])
            }
        }

        return clone;
    }
    return target;
}

怎麼可以更優雅的作類型判斷?

你據說過「循環引用「嗎?

假如目標對象的屬性間接或直接的引用了自身,就會造成循環引用,致使在遞歸的時候爆棧。
因此咱們的代碼須要循環檢測,設置一個Map用於存儲已拷貝過的對象,當檢測到對象已存在於Map中時,取出該值並返回便可避免爆棧。

function deepClone(target, map = new Map()) {
    const targetType = typeof target;
    if (targetType === 'object' || targetType === 'function') {
        let clone = Array.isArray(target)?[]:{};
        if (map.get(target)) {
            return map.get(target);
        }
        
        map.set(target, clone);

        if(Object.prototype.toString.call(target) === '[object Date]'){
            clone = new Date(target)
        }
        
        if(Object.prototype.toString.call(target) === '[object Object]'
            ||Object.prototype.toString.call(target) === '[object Array]'){
            for (const key in target) {
                clone[key] = deepClone(target[key],map)
            }
        }

        return clone;
    }
    return target;
}

好多教程使用 WeakMap 作存儲,相比Map,WeakMap好在哪兒?

通往優秀的階梯

以上咱們就完成了一個基礎的深拷貝。可是它僅僅是及格而已,想要作到優秀,還要處理一下以前留下的幾個問題。

獲取Symbol屬性

ES6Symbol類型也能夠做爲對象的 key ,可是for...inObject.keys(target)都拿不到 Symbol類型的屬性名。

好在咱們能夠經過Object.getOwnPropertySymbols(target) 獲取對象上全部的Symbol屬性,再結合for...inObject.keys()就可以拿到所有的 key。不過這種方式有些麻煩,有沒有更好用的方法?

有!Reflect.ownKeys(target) 正是這樣一個集優雅與強大與一身的方法。可是正如同人無完人,這個方法也不完美:顧名思義,ownKeys是拿不到原型鏈上的屬性的。因此須要結合具體場景來組合使用上述方法。

特殊的內置類型

DateError等特殊的內置類型雖然是對象,可是並不能遍歷屬性,因此針對這些類型須要從新調用對應的構造器進行初始化。JavaScript 內置了許多相似的特殊類型,然而咱們並非無情的 API 機器,面試中可以回答上述要點也就足夠了。

上述內置類型咱們均可以經過Object.prototype.toString.call(target) 的方式拿到,因此這裏能夠封裝一個類型判斷的方法用於判斷target 是否可以繼續遍歷,以便於及後續的處理。

然而 ES6 新增了Symbol.toStringTag方法,能夠用來自定義類名,這就致使 Object.prototype.toString.call(target)拿到的類型名也可能不夠準確:

class ValidatorClass {
  get [Symbol.toStringTag]() {
    return "Validator";
  }
}

Object.prototype.toString.call(new ValidatorClass()); 
// "[object Validator]"

使用WeakMap作循環檢測,比使用Map好在哪兒?

原生的WeakMap持有的是每一個鍵對象的「弱引用」,這意味着在沒有其餘引用存在時垃圾回收能正確進行。若是 target 很是龐大,那麼使用Map 後若是沒有進行手動釋放,這塊內存就會持續的被佔用。而WeakMap則不須要擔憂這個問題。

後記

若是上面幾個問題都獲得了妥善的處理,那麼這樣的深拷貝就能夠說是一個足夠打動面試官的深拷貝了。固然這個深拷貝還不夠優秀,有不少待完善的地方,相信善於思考的你已經有了本身的思路。

但本文的重點並不僅僅是實現一個深拷貝,更多的是但願它可以幫助你更好的理解面試官的思路,從而更好的發揮自身的能力。

參考資料

關注「JS漫步指南」公衆號,獲取更多面試祕籍!

相關文章
相關標籤/搜索