前言
在JavaScript中沒"子類”和“父類”的概念荷逞,進(jìn)一步地也沒有“類”和“實(shí)例”的的區(qū)分啄清。它靠一種看上去十分古怪的”原型鏈“(prototype chain)模式來(lái)實(shí)現(xiàn)繼承。學(xué)過(guò)JAVA等編程語(yǔ)言的人可能會(huì)認(rèn)為這是對(duì)Java等語(yǔ)言的繼承實(shí)現(xiàn)方式的一種拙劣并且失敗的模仿睦刃,然而事實(shí)并非如此车胡,原型鏈模式和我們常見的Class模式分別是兩種編程范式prototype_base和class_base的實(shí)現(xiàn)檬输,前者在動(dòng)態(tài)語(yǔ)言中似乎十分常見,而后者主要在靜態(tài)語(yǔ)言領(lǐng)域流行匈棘。下面是維基百科關(guān)于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
如何理解原型鏈
我們以一個(gè)名字叫Foo()
的函數(shù)為例丧慈。我們定義:
function Foo(){
}
然后再var f1 = new Foo()
,var f2 = new Foo()
,這期間都有什么事情發(fā)生呢?我們通過(guò)一張圖來(lái)看一下:
先介紹兩個(gè)概念:_proto_
和prototype
:
-
_proto_
:引用《JavaScript權(quán)威指南》中的說(shuō)明:
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.
就是說(shuō)就是每個(gè)JS對(duì)象一定對(duì)應(yīng)一個(gè)原型對(duì)象逃默,并從原型對(duì)象繼承屬性和方法鹃愤。既然有這么一個(gè)原型對(duì)象,那么對(duì)象怎么和它對(duì)應(yīng)的完域?如何描述這種對(duì)應(yīng)關(guān)系软吐?答案就是通過(guò)_proto_
,對(duì)象__proto__
屬性的值就是它所對(duì)應(yīng)的原型對(duì)象。
-
prototype
: 與_proto_
不同,prototype
是函數(shù)才有的屬性吟税。當(dāng)你創(chuàng)建函數(shù)時(shí)凹耙,JS會(huì)為這個(gè)函數(shù)自動(dòng)添加prototype
屬性,值是空對(duì)象乌妙。而一旦你把這個(gè)函數(shù)當(dāng)作構(gòu)造函數(shù)(constructor
)調(diào)用(即通過(guò)new
關(guān)鍵字調(diào)用)使兔,那么JS就會(huì)幫你創(chuàng)建該構(gòu)造函數(shù)的實(shí)例,實(shí)例繼承構(gòu)造函數(shù)prototype
的所有屬性和方法(實(shí)例通過(guò)設(shè)置自己的__proto__
指向承構(gòu)造函數(shù)的prototype
來(lái)實(shí)現(xiàn)這種繼承)藤韵。它的存在主要是為了存放共享的數(shù)據(jù)和實(shí)現(xiàn)繼承虐沥。
??下面結(jié)合上面的圖示來(lái)分析,我們可以看到function Foo()
對(duì)應(yīng)一個(gè)Foo.prototype
的原型泽艘,那么function Foo
和Foo.prototype
之間的關(guān)系是什么尼欲险?
??圖里其實(shí)已經(jīng)展示得很清楚了,functon Foo()
是Foo.prototype
的構(gòu)造函數(shù)匹涮,Foo.prototype
是function Foo()
的原型實(shí)例天试。當(dāng)我們使用new
關(guān)鍵字創(chuàng)建var f1 = new Foo()
,var f2 = new Foo()
后,f1然低、f2
中會(huì)有一個(gè)_proto_
字段指向Foo.prototype
,這種xxx._proto_._proto....
的指向就代表了原型鏈的結(jié)構(gòu)(應(yīng)該是個(gè)森林)喜每。同時(shí)每個(gè)函數(shù)function xxx()
其實(shí)都是通過(guò)function Function()
來(lái)創(chuàng)造的,所以function Foo()
的_proto_
應(yīng)該指向Function.prototype
,并且function Function()
自身的_proto_
也指向Function.prototype
雳攘。
??事實(shí)上带兜,所有function xxx()
的_proto_
最終都會(huì)指向Function.prototype
,而所有的xxx.prototype
最后都會(huì)指向Object.prototype
,最終指向null
吨灭。關(guān)于function Object()
這個(gè)函數(shù)其實(shí)有點(diǎn)像Java中的Object對(duì)象刚照,所有原型都會(huì)繼承自它的原型。這里有個(gè)有意思的問(wèn)題喧兄,function Function()
也是個(gè)函數(shù)无畔,所以function Function()
的_proto_
屬性的值為Function.prototype
,這也就意味著它自己創(chuàng)造了自己吠冤。這樣的結(jié)果就是function Object()._proto_ = Function.prototype
浑彰、而function Function()._proto._proto_ = Object.prototype
,即Object instanceof Function == true
、Function instanceof Object == true
翻譯過(guò)來(lái)就是Object
是Function
的實(shí)例拯辙,Function
是Object
的實(shí)例闸昨,這是一種類似先有雞還是先有蛋的蜜汁尷尬局面。
總結(jié):
- 所有對(duì)象的
_proto_
字段都指向創(chuàng)建它的構(gòu)造函數(shù)的原型, 最終都指向Object.prototype
,類似xxx.prototype._proto_._proto_..._proto_ = Object.prototype = null
就是原型鏈饵较。 - 所有函數(shù)都由
function Function()
創(chuàng)建拍嵌,所以所有函數(shù)的(包括它本身)_proto_
字段都會(huì)指向Function.prototype
,最后才指向Object.prototype
循诉。
使用原型鏈實(shí)現(xiàn)繼承
定義父函數(shù):
function Father() {
this.age = "56"; }
Father.prototype.say = function () {
alert("my age is "+this.age);
}
定義子函數(shù):
function Son() {
this.age = '26';
this.play = "football"; }
Son.prototype.play = function () {
alert("I like play "+this.play);
}
實(shí)現(xiàn)繼承后的原型鏈應(yīng)該是:Son.prototype._proto_ = Father.prototype
實(shí)現(xiàn)方式:借用第三個(gè)函數(shù)過(guò)渡
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;
}
測(cè)試驗(yàn)證:Son
的實(shí)例可以調(diào)用say()
則說(shuō)明繼承成功横辆。
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();
運(yùn)行結(jié)果:
繼承成功!