在Javascript中,萬物皆物件,但物件也有區別,大致可以分為兩類,即:普通物件(Object)和函式物件(Function)。
一般而言,通過new Function產生的物件是函式物件,其他物件都是普通物件。
舉例說明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function f1(){ //todo } var f2 = function(){ //todo }; var f3 = new Function('x','console.log(x)'); var o1 = {}; var o2 = new Object(); var o3 = new f1(); console.log( typeof f1,//function typeof f2,//function typeof f3,//function typeof o1,//object typeof o2,//object typeof o3 //object ); >> function function function object object object |
f1屬於函式的宣告,最常見的函式定義方式,f2實際上是一個匿名函式,把這個匿名函式賦值給了f2,屬於函式表示式,f3不常見,但也是一種函式物件。
Function是JS自帶的物件,f1,f2在建立的時候,JS會自動通過new Function()的方式來構建這些物件,因此,這三個物件都是通過new Function()建立的。
在Javascript中建立物件有兩種方式:物件字面量和使用new表示式,o1和o2的建立恰好對應了這兩種方式,重點講一下o3, 如果用Java和C#的思路來理解的話,o3是f1的例項物件,o3和f1是同一型別,至少我以前這麼認為,其實不然…
那麼怎麼理解呢? 很簡單,看o3是不是通過new Function產生的, 顯然不是,既然不是函式物件,那就是普通物件 。
通過對函式物件和普通物件的簡單理解之後,我們再來了解一下Javascript中的原型和原型鏈:
在JS中,每當建立一個函式物件f1 時,該物件中都會內建一些屬性,其中包括prototype和__proto__, prototype即原型物件,它記錄著f1的一些屬性和方法。
需要注意的是,prototype 對f1是不可見的,也就是說,f1不會查詢prototype中的屬性和方法。
1 2 3 |
function f(){} f.prototype.foo = "abc"; console.log(f.foo); //undefined |
那麼,prototype有什麼用呢? 其實prototype的主要作用就是繼承。 通俗一點講,prototype中定義的屬性和方法都是留給自己的“後代”用的,因此,子類完全可以訪問prototype中的屬性和方法。
想要知道f1是如何把prototype留給“後代”,我們需要了解一下JS中的原型鏈,此時,JS中的 __proto__ 入場了,這哥們長的很奇特,隱藏的也很深,以致於你經常見不到它,但它在普通物件和函式物件中都存在, 它的作用就是儲存父類的prototype物件,JS在通過new 表示式建立一個物件的時候,通常會把父類的prototype賦值給新物件的__proto__屬性,這樣,就形成了一代代傳承…
1 2 3 4 |
function f(){} f.prototype.foo = "abc"; var obj = new f(); console.log(obj.foo); //abc |
現在我們知道,obj中__proto__儲存的是f的prototype, 那麼f的prototype中的__proto__中儲存的是什麼呢? 看下圖:
如圖所示,f.prototype的__proto__中儲存的是Object.prototype,Object.prototype物件中也有__proto__,而從輸出結果看,Object.prototype.__proto__ 是null,表示obj物件原型鏈的終結。如下圖所示:
obj物件擁有這樣一個原型鏈以後,當obj.foo執行時,obj會先查詢自身是否有該屬性,但不會查詢自己的prototype,當找不到foo時,obj就沿著原型鏈依次去查詢…
在上面的例子中,我們在f的prototype上定義了foo屬性,這時obj就會在原型鏈上找到這個屬性並執行。
最後,用幾句話總結一下本文中涉及到的重點:
- 原型鏈的形成真正是靠__proto__ 而非prototype,當JS引擎執行物件的方法時,先查詢物件本身是否存在該方法,如果不存在,會在原型鏈上查詢,但不會查詢自身的prototype。
- 一個物件的__proto__記錄著自己的原型鏈,決定了自身的資料型別,改變__proto__就等於改變物件的資料型別。
- 函式的prototype不屬於自身的原型鏈,它是子類建立的核心,決定了子類的資料型別,是連線子類原型鏈的橋樑。
- 在原型物件上定義方法和屬性的目的是為了被子類繼承和使用。