開發中的疑問
有時候在網頁開發的過程中,我們會遇到這兩個函數:
Object.prototype.toString.call()
Object.keys()
有的前端小夥伴可能就會産生這樣的疑問:為什麼 keys 方法存在 Object 上而不是 Object. prototype 上?有什麼講究嗎?
答案是肯定的,本質上,就是 Object 原型方法和 Object 靜态方法的差別。
不過在這之前,我們需要複習一下構造函數、對象原型以及對象的建立方法。
構造函數與對象原型
如圖:
- 對象的 __proto__ 屬性就是對象的原型對象。
- 原型對象的 constructor 屬性也指向對象的構造函數。
- 構造函數的 prototype 屬性指向對象的原型對象。
對象的 constructor
隻有 prototype 才有 constructor,普通對象的 constructor 繼承自其原型對象的 constructor。 為了友善了解,下面我們不再提及到非原型對象的這個指針。
原型鍊
實際上,對象原型和構造函數也是一個對象,如果把它們放到“執行個體對象”的位置,它們也擁有自己的對象原型和構造函數,于是,原型鍊(套娃)便開始了。
思考一下:
- Function 以及 Function. prototype 的__proto__指向哪裡?
- Object 以及 Object. prototype 的__proto__指向哪裡?
- Foo(使用者自定義構造函數)以及 Foo. prototype 的__proto__指向哪裡?
指向如圖:
- 所有構造函數都是由 Function 建立的,是以 proto 指向 Function. prototype。
- 除了 Object. prototype,其它原型都是 {} ,而 {} 的 proto 顯然指向 Object. prototype。
- Object. prototype 與其它 prototype 不同,它是 [Object:null prototype]{} 而不是 {}。它的 proto 指向 null。
- null 是基本類型,不具備任何屬性。
建立對象的三種方法
- 使用大括号建立。這種建立方法隐式繼承了 Object。也就是說,效果和 new Object 是一樣的。普通的工廠函數建立的對象也屬于這一類。
- 使用構造函數或者類建立。需要使用關鍵字 new。并會繼承該構造函數的 prototye 屬性作為執行個體對象的 proto。
- 使用 Object. create 靜态方法建立。該方法接收一個對象(typeof 為 Object 的值,是以也允許參數為 null),作為建立對象的原型。如果 Object. create 的參數為 null,那麼它和 Object. prototype 的輩分是相當的。列印的結果都是 [Object:null prototype]{}。
原型方法與靜态方法
有了前面這些基礎知識的鋪墊,相信很快就能了解 Object. prototype 和 Object 靜态方法的差別,其實非常簡單。
幾乎所有的對象的原型鍊上都存在 Object. prototype,是以,放在 Object. prototype 這個對象的方法都能被大多數對象拿到。但是:
- 如果這個對象是通過 Object.create(null) 方式建立的,那麼它就和 Object. prototype 對象同級,無法通過繼承拿到 Object. prototype 對象的方法。
- 另外,在繼承的過程中,執行個體對象到達 Object. prototype 的原型鍊上的方法往往可能被重寫。
基于以上兩點,我們隻能避免直接調用執行個體對象的方法,而是通過類似 Object.prototype.xxx.call(obj,...args) 這樣長的代碼來保證使用的是 Object 的原型方法。
而 Object 靜态方法不管對什麼對象,都可以直接使用,而且也更簡短。
可以說,對象原型(尤其是比較長的原型鍊上)的方法背棄了其設計初衷:通過執行個體對象就可以調用到原型鍊上的方法。
在新的 EcmaScript 的文法的 Api 中,更多的是以靜态方法出現而不是原型方法,比如 Proxy、Reflect 的 Api,一方面就是為了避免上述問題,另一方面是為了避免一些沖突(開發者在原型上定義的方法剛好和新文法的名字一樣)。是以,不難了解,以後越來越多的 Api 都會選擇以靜态方法的形式出現,而不是原型方法。
作者:天氣好
連結:https://juejin.cn/post/7289415627620810815