前言
在JavaScript中沒"子類”和“父類”的概念,進一步地也沒有“類”和“實例”的的區分。它靠一種看上去十分古怪的”原型鏈“(prototype chain)模式來實現繼承。學過JAVA等編程語言的人可能會認為這是對Java等語言的繼承實現方式的一種拙劣并且失敗的模仿,然而事實并非如此,原型鏈模式和我們常見的Class模式分別是兩種編程范式prototype_base和class_base的實現,前者在動態語言中似乎十分常見,而后者主要在靜態語言領域流行。下面是維基百科關于prototype_base模式的介紹:
Prototype-based programming is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects via delegation that serve as prototypes. This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming. Delegation is the language feature that supports prototype-based programming.
Prototype object oriented programming uses generalized objects, which can then be cloned and extended. Using fruit as an example, a "fruit" object would represent the properties and functionality of fruit in general. A "banana" object would be cloned from the "fruit" object, and would also be extended to include general properties specific to bananas. Each individual "banana" object would be cloned from the generic "banana" object. Compare to the class-based paradigm, where a "fruit" class (not object) would be extended by a "banana" class
如何理解原型鏈
我們以一個名字叫Foo()
的函數為例。我們定義:
function Foo(){
}
然后再var f1 = new Foo()
,var f2 = new Foo()
,這期間都有什么事情發生呢?我們通過一張圖來看一下:
先介紹兩個概念:_proto_
和prototype
:
-
_proto_
:引用《JavaScript權威指南》中的說明:
Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first
object inherits properties from the prototype.
就是說就是每個JS對象一定對應一個原型對象,并從原型對象繼承屬性和方法。既然有這么一個原型對象,那么對象怎么和它對應的?如何描述這種對應關系?答案就是通過_proto_
,對象__proto__
屬性的值就是它所對應的原型對象。
-
prototype
: 與_proto_
不同,prototype
是函數才有的屬性。當你創建函數時,JS會為這個函數自動添加prototype
屬性,值是空對象。而一旦你把這個函數當作構造函數(constructor
)調用(即通過new
關鍵字調用),那么JS就會幫你創建該構造函數的實例,實例繼承構造函數prototype
的所有屬性和方法(實例通過設置自己的__proto__
指向承構造函數的prototype
來實現這種繼承)。它的存在主要是為了存放共享的數據和實現繼承。
??下面結合上面的圖示來分析,我們可以看到function Foo()
對應一個Foo.prototype
的原型,那么function Foo
和Foo.prototype
之間的關系是什么尼?
??圖里其實已經展示得很清楚了,functon Foo()
是Foo.prototype
的構造函數,Foo.prototype
是function Foo()
的原型實例。當我們使用new
關鍵字創建var f1 = new Foo()
,var f2 = new Foo()
后,f1、f2
中會有一個_proto_
字段指向Foo.prototype
,這種xxx._proto_._proto....
的指向就代表了原型鏈的結構(應該是個森林)。同時每個函數function xxx()
其實都是通過function Function()
來創造的,所以function Foo()
的_proto_
應該指向Function.prototype
,并且function Function()
自身的_proto_
也指向Function.prototype
。
??事實上,所有function xxx()
的_proto_
最終都會指向Function.prototype
,而所有的xxx.prototype
最后都會指向Object.prototype
,最終指向null
。關于function Object()
這個函數其實有點像Java中的Object對象,所有原型都會繼承自它的原型。這里有個有意思的問題,function Function()
也是個函數,所以function Function()
的_proto_
屬性的值為Function.prototype
,這也就意味著它自己創造了自己。這樣的結果就是function Object()._proto_ = Function.prototype
、而function Function()._proto._proto_ = Object.prototype
,即Object instanceof Function == true
、Function instanceof Object == true
翻譯過來就是Object
是Function
的實例,Function
是Object
的實例,這是一種類似先有雞還是先有蛋的蜜汁尷尬局面。
總結:
- 所有對象的
_proto_
字段都指向創建它的構造函數的原型, 最終都指向Object.prototype
,類似xxx.prototype._proto_._proto_..._proto_ = Object.prototype = null
就是原型鏈。 - 所有函數都由
function Function()
創建,所以所有函數的(包括它本身)_proto_
字段都會指向Function.prototype
,最后才指向Object.prototype
。
使用原型鏈實現繼承
定義父函數:
function Father() {
this.age = "56"; }
Father.prototype.say = function () {
alert("my age is "+this.age);
}
定義子函數:
function Son() {
this.age = '26';
this.play = "football"; }
Son.prototype.play = function () {
alert("I like play "+this.play);
}
實現繼承后的原型鏈應該是:Son.prototype._proto_ = Father.prototype
實現方式:借用第三個函數過渡
function extends(Child,Father){
var F = function(){};
F.prototype = Father.prototype;
//Child.prototype._proto_ = F.prototype = Father.prototype
Child.prototype = new F();
//原本Child.prototype.constructor = F,修改為Child
Child.prototype.constructor = Child;
}
測試驗證:Son
的實例可以調用say()
則說明繼承成功。
function Father() {
this.age = "56"; }
Father.prototype.say = function () {
alert("my age is "+this.age); }
function Son() {
this.age = '26';
this.play = "football"; }
Son.prototype.play = function () {
alert("I like play "+this.play); }
function excents(Child,Father) {
var F = function () {}
F.prototype = Father.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child; }
excents(Son,Father);
var son = new Son();
son.say();
運行結果:
繼承成功!