徹底理解JavaScript的原型與原型鏈
目錄
- 前言
- 基礎鋪墊
- prototype
- contructor屬性
- __proto__
- 原型鏈
- 提高
- 總結
- 後語
前言
原型與原型鏈知識歷來都是面試中考察的重點,說難不算太難,但要完全理解還是得下一定的功夫。先來看一道面試題開開胃口吧:
function User() {} User.prototype.sayHello = function() {} var u1 = new User(); var u2 = new User(); console.log(u1.sayHello === u2.sayHello); console.log(User.prototype.constructor); console.log(User.prototype === Function.prototype); console.log(User.__proto__ === Function.prototype); console.log(User.__proto__ === Function.__proto__); console.log(u1.__proto__ === u2.__proto__); console.log(u1.__proto__ === User.__proto__); console.log(Function.__proto__ === Object.__proto__); console.log(Function.prototype.__proto__ === Object.prototype.__proto__); console.log(Function.prototype.__proto__ === Object.prototype);
基礎鋪墊
所有的物件本質上都是通過new 函式建立的,包括物件字面量的形式定義物件(相當於new Object()的語法糖)。
所有的函式本質上都是通過new Function建立的,包括Object、Array等
所有的函式都是物件。
prototype
每個函式都有一個屬性prototype,它就是原型,預設情況下它是一個普通Object物件,這個物件是呼叫該建構函式所建立的例項的原型。
contructor屬性
Script同樣存在由原型指向建構函式的屬性:constructor,即Func.prototype.constructor --> Func
__proto__
JavaScript中所有物件(除了null)都具有一個__proto__屬性,該屬性指向該物件的原型。
function User() {} var u1 = new User(); // u1.__proto__ -> User.prototype console.log(u1.__proto__ === User.prototype) // true
顯而易見,例項的__proto__屬性指向了建構函式的原型,那麼多個例項的__proto__會指向同一個原型嗎?
var u2 = new User(); console.log(u1.__proto__ === u2.__proto__) // true
如果多個例項的__proto__都指向建構函式的原型,那麼例項如果能通過一種方式,訪問原型上的方法,屬性等,就可以在原型進行,實現程式設計客棧繼承的效果。
我們繼續更新一下原型與原型鏈的關係圖:
原型鏈
例項物件在nXqZtdIU查詢屬性時,如果查詢不到,就會沿著__proto__去與物件關聯的原型上查詢,如果還查詢不到,就去找原型的原型,直至查到最頂層,這也就是原型鏈的概念。
就藉助面試題,舉幾個原型鏈的例子:
舉例
- u1.sayHello():
u1上是沒有sayHello方法的,因此訪問u1.__proto__(User.prototype),成功訪問到sayHello方法 - u2.toString()
u2,User.prototype都沒有toString方法,User.prototype也是一個普通物件,因此繼續尋找User.prototype.__proto__(Object.prototype),成功呼叫到toString方法
提高
學完上面那些,大多數面試題都可以做出來了,例如下面這種
function A() {} function B(a) { this.a = a; } function C(a) { if (a) { this.a = a; } } A.prototype.a = 1; B.prototype.a = 1; C.prototype.a = 1; console.log(new A().a); //1 console.log(new B().a); //undefined console.log(new C(2).a); //2
但距離解決文章的最初的面試題還欠缺些什麼,比如Function.__proto__ === Object.__proto__、Function.prototype.__proto__ === Object.prototype.__proto__等,接著我們來一一攻克它。
Objcet.__proto__ 、 Object.prototype、Object.prototype.__proto__
- Object是建構函式,在第二部分我們講過所有的函式都是通過new Function建立了,因此Object相當於Function的例項,即Object.__proto__ --> Function.prototype。
- Object.prototype是Object建構函式的原型,處於原型鏈的頂端,Object.prototype.__proto__已經沒有可以指向的上層原型,因此其值為null
// 總結: Object.__proto__ --> Function.prototype Object.prototype.__proto__ --> null
Function.__proto__、Function.prototype、Function.prototype.__proto__
- Function.prototype是Function的原型,是所有函式例項的原型,例如上面講的Object.__proto__
- Function.prototype是一個普通物件,因此Function.prototype.__proto__ --> Object.prototype
- Function.__proto__: __proto__指向創造它的建構函式的原型,那誰創造了Function那?
- 猜想:函式物件也是物件,那Function.__proto__會指向Object.prototype嗎?上文提到,Object.__proto__ --> Function.prototype。如果Function.__proto__ -> Object.prototype,感覺總是怪怪的,到底誰創造了誰,於是我去做了一下測試:
實踐證明只存在Object.__proto__ --> Function.prototype
苦思冥想沒得出結果,難道Function函式是猴子不成,從石頭縫裡面蹦出來的?於是我進行了一同亂七八糟的測試,沒想到找出了端倪。
通過上面我們可以得出:Function.__proto__ --> Function.prototype
Function函式不通過任何東西建立,引擎啟動時,新增到記憶體中
總結
最後將原型與原型鏈方面的知識凝結成一張圖:
-
程式設計客棧
- 所有函式(包括Function)的__proto__指向Function.prototype
- 自定義物件例項的__proto__指向建構函式的原型
- 函式的prototype的__proto__指向Object.prototype
- Object.prototype.__proto__ --> null
後語
知識的海洋往往比想象中還要遼闊,原型與原型鏈這邊也反覆的學過多次,我認為應該學的比較全面,比較完善了。但遇到這個面試題後,我才發現我所學的只不過是一根枝幹,JS裡面真的有很多深層次的寶藏等待挖掘。學海無涯,與君共勉。
最後再附贈個簡單的面試題,提高一下自信:
var F = function () {} Object.prototype.a = function () {} Function.prototype.b = function () {} var f = new F(); console.log(f.a,f.b,F.a,F.b); // 原型鏈 // f.__proto__ --> F.prototype --> Object.prototype // F.__proto__ --> Function.prototype --> Object.prototype
到此這篇關於JavaScript原型與原型鏈的文章就介紹到這了,更多相關JavaScript原型與原型鏈內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!