天天看點

為什麼是 Object.keys 而不是 Object.prototype.keys ?

作者:前端分享官

開發中的疑問

有時候在網頁開發的過程中,我們會遇到這兩個函數:

Object.prototype.toString.call()
Object.keys()           

有的前端小夥伴可能就會産生這樣的疑問:為什麼 keys 方法存在 Object 上而不是 Object. prototype 上?有什麼講究嗎?

答案是肯定的,本質上,就是 Object 原型方法和 Object 靜态方法的差別。

不過在這之前,我們需要複習一下構造函數、對象原型以及對象的建立方法。

構造函數與對象原型

如圖:

為什麼是 Object.keys 而不是 Object.prototype.keys ?
  1. 對象的 __proto__ 屬性就是對象的原型對象。
  2. 原型對象的 constructor 屬性也指向對象的構造函數。
  3. 構造函數的 prototype 屬性指向對象的原型對象。

對象的 constructor

隻有 prototype 才有 constructor,普通對象的 constructor 繼承自其原型對象的 constructor。 為了友善了解,下面我們不再提及到非原型對象的這個指針。

原型鍊

實際上,對象原型和構造函數也是一個對象,如果把它們放到“執行個體對象”的位置,它們也擁有自己的對象原型和構造函數,于是,原型鍊(套娃)便開始了。

為什麼是 Object.keys 而不是 Object.prototype.keys ?

思考一下:

  1. Function 以及 Function. prototype 的__proto__指向哪裡?
  2. Object 以及 Object. prototype 的__proto__指向哪裡?
  3. Foo(使用者自定義構造函數)以及 Foo. prototype 的__proto__指向哪裡?

指向如圖:

為什麼是 Object.keys 而不是 Object.prototype.keys ?
  1. 所有構造函數都是由 Function 建立的,是以 proto 指向 Function. prototype。
  2. 除了 Object. prototype,其它原型都是 {} ,而 {} 的 proto 顯然指向 Object. prototype。
  3. Object. prototype 與其它 prototype 不同,它是 [Object:null prototype]{} 而不是 {}。它的 proto 指向 null。
  4. null 是基本類型,不具備任何屬性。

建立對象的三種方法

  1. 使用大括号建立。這種建立方法隐式繼承了 Object。也就是說,效果和 new Object 是一樣的。普通的工廠函數建立的對象也屬于這一類。
  2. 使用構造函數或者類建立。需要使用關鍵字 new。并會繼承該構造函數的 prototye 屬性作為執行個體對象的 proto。
  3. 使用 Object. create 靜态方法建立。該方法接收一個對象(typeof 為 Object 的值,是以也允許參數為 null),作為建立對象的原型。如果 Object. create 的參數為 null,那麼它和 Object. prototype 的輩分是相當的。列印的結果都是 [Object:null prototype]{}。

原型方法與靜态方法

有了前面這些基礎知識的鋪墊,相信很快就能了解 Object. prototype 和 Object 靜态方法的差別,其實非常簡單。

幾乎所有的對象的原型鍊上都存在 Object. prototype,是以,放在 Object. prototype 這個對象的方法都能被大多數對象拿到。但是:

  1. 如果這個對象是通過 Object.create(null) 方式建立的,那麼它就和 Object. prototype 對象同級,無法通過繼承拿到 Object. prototype 對象的方法。
  2. 另外,在繼承的過程中,執行個體對象到達 Object. prototype 的原型鍊上的方法往往可能被重寫。

基于以上兩點,我們隻能避免直接調用執行個體對象的方法,而是通過類似 Object.prototype.xxx.call(obj,...args) 這樣長的代碼來保證使用的是 Object 的原型方法。

而 Object 靜态方法不管對什麼對象,都可以直接使用,而且也更簡短。

可以說,對象原型(尤其是比較長的原型鍊上)的方法背棄了其設計初衷:通過執行個體對象就可以調用到原型鍊上的方法。

在新的 EcmaScript 的文法的 Api 中,更多的是以靜态方法出現而不是原型方法,比如 Proxy、Reflect 的 Api,一方面就是為了避免上述問題,另一方面是為了避免一些沖突(開發者在原型上定義的方法剛好和新文法的名字一樣)。是以,不難了解,以後越來越多的 Api 都會選擇以靜态方法的形式出現,而不是原型方法。

作者:天氣好

連結:https://juejin.cn/post/7289415627620810815