首先要搞明白幾個概念:
- 函式(function)
- 函式物件(function object)
- 本地物件(native object)
- 內建物件(build-in object)
- 宿主物件(host object)
函式
1 2 3 4 5 6 |
function foo(){ } var foo = function(){ } |
前者為函式宣告,後者為函式表示式。typeof foo
的結果都是function。
函式物件
函式就是物件,代表函式的物件就是函式物件
官方定義, 在Javascript中,每一個函式實際上都是一個函式物件.JavaScript程式碼中定義函式,或者呼叫Function建立函式時,最終都會以類似這樣的形式呼叫Function函式:var newFun = new Function(funArgs, funBody)
其實也就是說,我們定義的函式,語法上,都稱為函式物件,看我們如何去使用。如果我們單純的把它當成一個函式使用,那麼它就是函式,如果我們通過他來例項化出物件來使用,那麼它就可以當成一個函式物件來使用,在物件導向的範疇裡面,函式物件類似於類的概念。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var foo = new function(){ } typeof foo // object 或者 function Foo (){ } var foo = new Foo(); typeof foo // object |
上面,我們所建立的物件
本地物件
ECMA-262 把本地物件(native object)定義為“獨立於宿主環境的 ECMAScript 實現提供的物件”。簡單來說,本地物件就是 ECMA-262 定義的類(引用型別)。它們包括:
Object,Function,Array,String,Boolean,Number
Date,RegExp,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError.
我們不能被他們起的名字是本地物件,就把他們理解成物件(雖然是事實上,它就是一個物件,因為JS中萬物皆為物件),通過
1 2 3 4 5 6 |
typeof(Object) typeof(Array) typeof(Date) typeof(RegExp) typeof(Math) |
返回的結果都是function
也就是說其實這些本地物件(類)是通過function建立起來的,
1 2 3 4 5 6 7 |
function Object(){ } function Array(){ } ... |
可以看出Object原本就是一個函式,通過new Object()之後例項化後,建立物件。類似於JAVA中的類。
內建物件
ECMA-262 把內建物件(built-in object)定義為“由 ECMAScript 實現提供的、獨立於宿主環境的所有物件,在 ECMAScript 程式開始執行時出現”。這意味著開發者不必明確例項化內建物件,它已被例項化了。ECMA-262 只定義了兩個內建物件,即 Global 和 Math (它們也是本地物件,根據定義,每個內建物件都是本地物件)。
理清楚了這幾個概念,有助於理解我們下面要講述的原型和原型鏈。
prototype
prototype屬性是每一個函式都具有的屬性,但是不是一個物件都具有的屬性。比如
1 2 3 4 5 |
function Foo(){ } var foo = new Foo(); |
其中Foo中有prototype屬性,而foo沒有。但是foo中的隱含的__proto__屬性指向Foo.prototype。
1 |
foo.__proto__ === Foo.prototype |
為什麼會存在prototype屬性?
Javascript裡面所有的資料型別都是物件,為了使JavaScript實現物件導向的思想,就必須要能夠實現‘繼承’使所有的物件連線起來。而如何實現繼承呢?JavaScript採用了類似C++,java的方式,通過new的方式來實現例項。
舉個例子,child1,child2都是Mother的孩子,且是雙胞胎。(雖然不是很好,但是還是很能說明問題的)
1 2 3 4 5 6 |
function Mother(name){ this.name = name; this.father = 'baba'; } var child1 = new Mother('huahua'); var child2 = new Mother('huihui'); |
如果有一天,發現孩子的父親其實是Baba,那麼就要對孩子每一個孩子的father屬性。
1 2 |
child1.father ='Baba'; console.log(child2.father) // baba |
也就是說修改了其中一個孩子的father屬性不會影響到下一個,屬性的值無法共享。
正是這個原因才提出來prototype屬性,把需要共享的屬性放到建構函式也就是父類的例項中去。
__proto__
__proto__屬性是每一個物件以及函式都隱含的一個屬性。對於每一個含有__proto__屬性,他所指向的是建立他的建構函式的prototype。原型鏈就是通過這個屬性構件的。
想像一下,如果一個函式物件(也成為建構函式)a的prototype是另一個函式物件b構件出的例項,a的例項就可以通過__proto__與b的原型鏈起來。而b的原型其實就是Object的例項,所以a的例項物件,就可以通過原型鏈和object的prototype連結起來。
1 2 3 4 5 6 7 8 9 10 11 12 |
function a(){ } function b(){ } var b1 = new b(); a.prototype = b1; var a1 = new a(); console.log(a1.__proto__===b1);//true console.log(a1.__proto__.__proto__===b.prototype) //true console.log(a1.__proto__.__proto__.__proto__===Object.prototype) //true |
如果要理清原型和原型鏈的關係,首先要明確一下幾個概念:
1.JS中的所有東西都是物件,函式也是物件, 而且是一種特殊的物件
2.JS中所有的東西都由Object衍生而來, 即所有東西原型鏈的終點指向Object.prototype
3.JS物件都有一個隱藏的__proto__屬性,他指向建立它的建構函式的原型,但是有一個例外,Object.prototype.__proto__指向的是null。
4.JS中建構函式和例項(物件)之間的微妙關係
建構函式通過定義prototype來約定其例項的規格, 再通過 new 來構造出例項,他們的作用就是生產物件.
1 2 3 4 5 6 |
function Foo(){ } var foo = new Foo(); foo其實是通過Foo.prototype來生成例項的。 |
建構函式本身又是方法(Function)的例項, 因此也可以查到它的__proto__(原型鏈)
1 2 3 4 5 |
function Foo(){ } 等價於 var Foo= new Function(); |
而Function實際上是
1 2 3 4 5 |
function Function(){ Native Code } 也就是等價於 var Function= new Function(); |
所以說Function是通過自己建立出來的。正常情況下物件的__proto__是指向建立它的建構函式的prototype的.所以Function的__proto__指向的Function.prototype
Object 實際上也是通過Function建立出來的
1 2 3 4 5 6 7 |
typeof(Object)//function 所以, function Object(){ Native Code } 等價於 var Object = new Function(); |
那麼Object的__proto__指向的是Function.prototype,也即是
1 |
Object.__proto__ === Function.prototype //true |
下面再來看Function.prototype的__proto__指向哪裡
因為JS中所有的東西都是物件,那麼,Function.prototype 也是物件,既然是物件,那麼Function.prototype肯定是通過Object建立出來的,所以,
1 |
Function.prototype.__proto__ === Object.prototype //true |
綜上所述,Function和Object的原型以及原型鏈的關係可以歸納為下圖。
對於單個的物件例項,如果通過Object建立,
1 |
var obj = new Object(); |
如果通過函式物件來建立,
1 2 3 4 |
function Foo(){ } var foo = new Foo(); |
那麼它的原型和原型鏈的關係如下圖