本文研究一下Javascript的核心基礎——原型鏈和繼承。javascript
對於使用過基於類的語言(如Java或C#)的人來講,Javascript的繼承有點難以搞懂,由於它自己沒有class
這種東西。(ES6中引入了class
關鍵字,看上去也像傳統的OOP語言,可是那只是語法糖,底層仍是基於原型)。java
MDN上對於原型鏈的解釋:數組
當談到繼承時,JavaScript 只有一種結構:對象。每一個實例對象( object )都有一個私有屬性(稱之爲__proto__
)指向它的構造函數的原型對象(prototype
)。該原型對象也有一個本身的原型對象(__proto__
) ,層層向上直到一個對象的原型對象爲null
。根據定義,null
沒有原型,並做爲這個 原型鏈中的最後一個環節。幾乎全部 JavaScript 中的對象都是位於原型鏈頂端的
Object
的實例。網絡
這段話可能難以理解,咱們來舉個例子:數據結構
const list = []; // 定義數組 list.__proto__ === Array.prototype; // true list.__proto__.__proto__ === Object.prototype; // true list.__proto__.__proto__.__proto__===null; // true // 繼承關係爲 // list -> Array.prototype -> Object.prototype -> null
結合MDN的解釋,咱們來解釋一下上述例子:函數
list是Array
的實例對象,使用了字面量
的方式建立了對象實例
。this
每一個實例對象( object )都有一個私有屬性(稱之爲__proto__
)指向它的構造函數的原型對象(prototype
)。
// list的構造函數是Array,因此list.__proto__指向構造函數Array的原型對象。 list.__proto__ === Array.prototype; // true
該原型對象也有一個本身的原型對象(
__proto__
)
// Array.prototype也是對象,也有本身的原型對象,原型是Object.prototype // 下面是數學運算(等量代換) // list.__proto__ = Array.prototype // Array.prototype.__proto__ = Object.prototype list.__proto__.__proto__ === Object.prototype; // true
層層向上直到一個對象的原型對象爲null
。根據定義,null
沒有原型,並做爲這個 原型鏈中的最後一個環節。
// 目前咱們來到了Object.prototype,根據規範,Object.prototype的原型對象爲null // list.__proto__ = Array.prototype // Array.prototype.__proto__ = Object.prototype // Object.prototype.__proto__ = null; list.__proto__.__proto__.__proto__ === null; // true
當咱們訪問對象的屬性或者方法時,會先從對象自己開始查找,若是查找不到,則查找對象的
__proto__
,層層向上查找,直到查找到屬性,不然拋出錯誤。
const list = []; list.toString();
屬性查找過程以下:spa
__proto__
,也就是Array.prototype
,找到了Array.prototype.toString
__proto__
= 對象構造函數.prototype
Object.prototype
優先返回
機制。通過原型鏈的簡單介紹,相信你們對原型和原型鏈有了一個比較直觀的瞭解了,如今要說到的是函數。prototype
咱們知道,Javascript中函數也是對象,因此Function.__proto__
指向Object.prototype
。
上面的結論在Javascript中是有問題
的。咱們來聊一聊函數。code
先看看簡單一點的例子,你們知道,Object
是對象的構造函數
,構造函數
也是函數
,全部的函數
的原型都是Function.prototype
,因此Object.__proto__
是等於Function.prototype
的。
事實證實,也是如此。
那麼Function.__proto__
爲何不等於Object.prototype
呢?Function
不是對象嗎?
Function確實是對象,同時仍是構造函數,能夠經過new Function()來獲得函數實例。
上面咱們說到全部函數
的原型是Function.prototype
,因此Function這個構造函數
的原型__proto__
等於Function.prototype
。
基於以上原理,還有如下相等關係:
Object.__proto__ === Function.prototype
Array.__proto__ === Function.prototype
咱們知道Function.__proto__
是指向Function.prototype
,那個Function.prototype
這個Function
哪裏來的?Function
本身創造本身?那不是會死循環嗎?
這個問題不是純JS層面能解決的,牽涉到底層實現,下面是網絡上別人整理的結論,有須要的能夠研究一下V8的源碼,這樣能夠完全解決這個問題。
- 用C/C++ 構造內部數據結構建立一個 OP 即(Object.prototype)以及初始化其內部屬性但不包括行爲。
- 用 C/C++ 構造內部數據結構建立一個 FP 即(Function.prototype)以及初始化其內部屬性但不包括行爲。
- 將 FP 的[[Prototype]]指向 OP。
- 用 C/C++ 構造內部數據結構建立各類內置引用類型。
- 將各內置引用類型的[[Prototype]]指向 FP。
- 將 Function 的 prototype 指向 FP。
- 將 Object 的 prototype 指向 OP。
- 用 Function 實例化出 OP,FP,以及 Object 的行爲並掛載。
- 用 Object 實例化出除 Object 以及 Function 的其餘內置引用類型的 prototype 屬性對象。
- 用 Function 實例化出除Object 以及 Function 的其餘內置引用類型的 prototype 屬性對象的行爲並掛載。
- 實例化內置對象 Math 以及 Grobal
- 至此,全部 內置類型構建完成。
Function.protype
,構造函數也是函數,因此構造函數的原型也是Function.prototype
下面是一道有點難度的JS基礎題,能夠感覺一下:
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); console.log(new B().a); console.log(new C().a);
輸出是
1 undefined 1
爲何輸出1
?
由於new A()這個對象上沒有屬性a,因此去查找原型鏈,查到了F.prototype.a
爲何輸出undefined
?
由於new B時沒有傳遞a,因此a是undefined,new B()這個對象是有a屬性的,只不過值是undefined,因此不查原型鏈
爲何輸出1
?
由於new C()未傳遞a,因此a是undefined,因爲if(a)的判斷,new C()這個對象內部沒有a屬性,因此去查原型鏈
function F() { this.a = 1; } F.prototype.b = 2; var f = new F(); console.log(f.hasOwnProperty('a')); console.log(f.hasOwnProperty('b'));
輸出是
true false
輸出true比較好理解,由於構造函數F
聲明瞭屬性a
,因此F
的實例有a
屬性
false
?b是f
的原型對象F.prototype
的屬性,不是b
本身的,不能拿別人的說成本身的。
本文研究了原型和原型鏈之間的關係以及常見對象的原型和原型鏈,對於特殊對象Function也研究了一下,若是能搞懂後面兩個問題,那本文對你來講沒什麼問題了。