說(shuō)起OO,我們首先想到的肯定是抽象,繼承,多態(tài),重載等一系列的名詞。而在JavaScript語(yǔ)言中,因?yàn)樗腔趯?duì)象的(Object-Based),并不是面向?qū)ο蟮模╫bject-oriented),所有它并沒(méi)有提供這些能力。它僅僅具有一些面向?qū)ο蟮幕镜奶卣鞫?比如您可以使用
new 語(yǔ)句來(lái)調(diào)用一個(gè)構(gòu)造函數(shù))。
但在prototype中通過(guò)一些巧妙的方式實(shí)現(xiàn)了對(duì)象的抽象與繼承, 通過(guò)使用Object.extend方法,它甚至可以允許我們實(shí)現(xiàn)多重繼承.
下面我們來(lái)看Enumerable的each()方法實(shí)現(xiàn):
第一步:
each: function(iterator) {
var index = 0;
try {
this._each(function(value) {
try {
iterator(value, index++);
} catch (e) {
if (e != $continue) throw e;
}
});
} catch (e) {
if (e != $break) throw e;
}
}
第二步
Object.extend(Element.ClassNames.prototype, Enumerable);
我們看到第一步中each方法內(nèi)部調(diào)用到this._each方法,但this._each方法在Enumerable并未做定義,而是將其定義放在Element.ClassNames.prototype中。第二步就是使用Object.extend擴(kuò)展Element.ClassNames.prototype的內(nèi)容。整個(gè)過(guò)程從Java的角度,相當(dāng)于定義一個(gè)抽象類Enumerable,它提供了對(duì)枚舉對(duì)象的各種迭代操作,但迭代內(nèi)容的方式卻通過(guò)抽象的方法_each()下放給子類做具體實(shí)現(xiàn), 充分實(shí)現(xiàn)了抽象與繼承的功能。整個(gè)效果還有另一種實(shí)現(xiàn)方式是利用Adapter Pattern來(lái)實(shí)現(xiàn)轉(zhuǎn)調(diào)用,但這已經(jīng)不是抽象與繼承的范疇了。
上面例子中我們注意到最重要的一步是Object.extend這個(gè)方法的調(diào)用了, 看這個(gè)方法的源代碼我們發(fā)現(xiàn),這僅僅是子類型對(duì)父類型的所有property的拷貝,看似簡(jiǎn)單,但它卻幫我們豐富了代碼重用的方式,也利于我們用OO的思維觀察一個(gè)類型了。另外,關(guān)于多態(tài)在JS中談是沒(méi)有意義的,重載JS默認(rèn)就支持了,只是在調(diào)用父類同名方法里需要通過(guò)call方法來(lái)完成,但在prototype中并未做這種重載的實(shí)現(xiàn), 這里就不再多做討論了,有興趣的朋友可以交流。
此外,prototype中還提供了Class這個(gè)類型,它只包含一個(gè)create方法用于創(chuàng)建一個(gè)function對(duì)象,并提供默認(rèn)的初始化構(gòu)造函數(shù)initialize。乍看之下,這個(gè)方法功能很是簡(jiǎn)單,用處不大,我們完全可以通過(guò)JS提供的new function()的方式來(lái)實(shí)現(xiàn)。但其實(shí)作者的巧妙就是在于此,它將一個(gè)類型(class)的定義跟一個(gè)方法(function)的定義以一種顯示編碼的方式來(lái)作了區(qū)分,很有利于代碼的后期維護(hù),建議使用prototype框架的朋友盡量使用Class.create創(chuàng)建你的自定類型。
最后,我們來(lái)關(guān)注一下Function.prototype.bind與Function.prototype.bindAsEventListener這兩個(gè)方法, 在prototype中許多自定義對(duì)象的封裝, 或者我們自定義的對(duì)象封裝中都大量使用到了這兩個(gè)方法, bind方法主要的功能就是應(yīng)用某一對(duì)象的一個(gè)方法,并用另一個(gè)對(duì)象替換當(dāng)前對(duì)象。bindAsEventListener類似,但它只提供event做為入口參數(shù)。它們主要使用了javascript的apply方法與call方法,不明白可以先看我寫的API Document中的Function類中對(duì)這兩個(gè)方法的定義說(shuō)明與示例。
接著先來(lái)看prototype源代碼中對(duì) Function.prototype.bind 的定義
Function.prototype.bind = function() {
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
}
bind方法return的是一個(gè)function(),在function()函數(shù)內(nèi)部又用_method.apply的方法做一個(gè)轉(zhuǎn)調(diào)用。這種封裝其實(shí)是把a(bǔ)pply的綁定時(shí)即運(yùn)行封裝成調(diào)用時(shí)才綁定后運(yùn)行的方式,這樣的一個(gè)封裝非常有利于我們實(shí)現(xiàn)對(duì)象的組件化實(shí)現(xiàn)。有寫過(guò)的Microsoft的Behavior的朋友知道,Microsoft的支持這個(gè)技術(shù),其實(shí)就是一種把腳本程序完全從HTML頁(yè)面中分離出來(lái)的方法,它對(duì)頁(yè)面元素直接定義行為腳本,在行為腳本中可以以this的引用直接取得元素。而在低版本的瀏覽器不支持apply方法與call方法時(shí)候,我們很難以做到這點(diǎn),常常需要以附加屬性,全局變量等一些方式做中間數(shù)據(jù)保存與交換,耦合度高,聚合度低,舉個(gè)簡(jiǎn)單的例子來(lái)觀察一下:
例子: 我們想讓一個(gè)的BOTTON元素賦于一些行為" 累計(jì)自身被點(diǎn)擊的次數(shù)"
第一種方法是最簡(jiǎn)單的,我們直接給這個(gè)botton元素添加onclick方法來(lái)累加(如下:代碼1)。但這種方式只能應(yīng)用在一些簡(jiǎn)單的前臺(tái)腳本上面,如果我們需要寫一個(gè)具有大量行為的元素時(shí),比如寫一個(gè)Rich Table,我們就希望它最好應(yīng)該是個(gè)組件了。這樣,我我們只要通過(guò)一些簡(jiǎn)單的定義就可以使它自動(dòng)具有一系列的行為。
代碼1: <input type="button" value="100" onclick="this.value = parseInt(this.value) + 1"/>
第二種方法(代碼2: ) 我們通過(guò)一個(gè)普通function定義一個(gè)類型,初始化這個(gè)類型,傳入要產(chǎn)生行為作用的按鈕ID,被指定的按鈕就自動(dòng)具有" 累計(jì)自身被點(diǎn)擊的次數(shù)"的功能了,有效的實(shí)現(xiàn)了HTML元素與腳本代碼的執(zhí)。
代碼2:
<script language="javascript" src="prototype.js"></script>
<SCRIPT language="javascript">
window.onload = function(){
new NonBindClass("btn1");
}
var NonBindClass_Current_Element = null;
function NonBindClass(element){
this.element = $(element);
if(!(this.element || false)){ return null;}
this.element.attachEvent('onclick',printValue);
NonBindClass_Current_Element = this.element;
function printValue(){
//注意,這里的this并不是發(fā)生當(dāng)前動(dòng)作的主體或者當(dāng)前NonBindClass的實(shí)例的引用! 所以我通過(guò)中間變量或全局變量的方式來(lái)取得元素
NonBindClass_Current_Element.value = parseInt(NonBindClass_Current_Element.value) + 1;
}
}
</SCRIPT>
<input id="btn1" type="button" value="100"/>
但是這里我們看到printValue方法中使用NonBindClass_Current_Element這個(gè)全局變量來(lái)取得定義元素的對(duì)象, 但這種全局變量常常讓人無(wú)比的頭痛! 而且, 這時(shí)候如果需要給另一個(gè)按鈕btn3也添加同樣行為,那么全局變量NonBindClass_Current_Element會(huì)被重新指向到btn3,而對(duì)于btn1的點(diǎn)擊事件也會(huì)隨之作用到btn3,使得我們無(wú)法達(dá)到目的。當(dāng)然,我們可以通過(guò)其它的方式來(lái)達(dá)到目的,比如讓printValue方法內(nèi)部可以直接引用到指定的對(duì)象: 一種辦法就是傳入?yún)?shù),但這種方法在做attachEvent方法綁定時(shí)會(huì)直接做調(diào)用到printValue方法! 還有一種辦法 就是使用prototype的bind方法,我們可以給printValue綁定任意的對(duì)象,bind(this)或者bind(this.element)都可以達(dá)到目的,具體請(qǐng)看 代碼3:
代碼3:
<script language="javascript" src="prototype.js"></script>
<SCRIPT language="javascript">
window.onload = function(){
new BindClass("btn1");
new BindClass("btn2");
}
function BindClass(element){
this.element = $(element);
if(!(this.element || false)){ return null;}
this.element.attachEvent('onclick',printValue.bind(this.element));
function printValue(obj){
this.value = parseInt(this.value) + 1;
}
}
</SCRIPT>
<input id="btn1" type="button" value="100"/>
<input id="btn2" type="button" value="200"/>
代碼更加精簡(jiǎn),內(nèi)聚度也高,并且支持給多個(gè)不同的元素賦予同樣行為。
Function.prototype.bindAsEventListener的功能類似,差別在于它只傳入固定的一個(gè)event參數(shù),用于事件后期運(yùn)行時(shí)綁定調(diào)用.
但在prototype中通過(guò)一些巧妙的方式實(shí)現(xiàn)了對(duì)象的抽象與繼承, 通過(guò)使用Object.extend方法,它甚至可以允許我們實(shí)現(xiàn)多重繼承.
下面我們來(lái)看Enumerable的each()方法實(shí)現(xiàn):
第一步:
each: function(iterator) {
var index = 0;
try {
this._each(function(value) {
try {
iterator(value, index++);
} catch (e) {
if (e != $continue) throw e;
}
});
} catch (e) {
if (e != $break) throw e;
}
}
第二步
Object.extend(Element.ClassNames.prototype, Enumerable);
我們看到第一步中each方法內(nèi)部調(diào)用到this._each方法,但this._each方法在Enumerable并未做定義,而是將其定義放在Element.ClassNames.prototype中。第二步就是使用Object.extend擴(kuò)展Element.ClassNames.prototype的內(nèi)容。整個(gè)過(guò)程從Java的角度,相當(dāng)于定義一個(gè)抽象類Enumerable,它提供了對(duì)枚舉對(duì)象的各種迭代操作,但迭代內(nèi)容的方式卻通過(guò)抽象的方法_each()下放給子類做具體實(shí)現(xiàn), 充分實(shí)現(xiàn)了抽象與繼承的功能。整個(gè)效果還有另一種實(shí)現(xiàn)方式是利用Adapter Pattern來(lái)實(shí)現(xiàn)轉(zhuǎn)調(diào)用,但這已經(jīng)不是抽象與繼承的范疇了。
上面例子中我們注意到最重要的一步是Object.extend這個(gè)方法的調(diào)用了, 看這個(gè)方法的源代碼我們發(fā)現(xiàn),這僅僅是子類型對(duì)父類型的所有property的拷貝,看似簡(jiǎn)單,但它卻幫我們豐富了代碼重用的方式,也利于我們用OO的思維觀察一個(gè)類型了。另外,關(guān)于多態(tài)在JS中談是沒(méi)有意義的,重載JS默認(rèn)就支持了,只是在調(diào)用父類同名方法里需要通過(guò)call方法來(lái)完成,但在prototype中并未做這種重載的實(shí)現(xiàn), 這里就不再多做討論了,有興趣的朋友可以交流。
此外,prototype中還提供了Class這個(gè)類型,它只包含一個(gè)create方法用于創(chuàng)建一個(gè)function對(duì)象,并提供默認(rèn)的初始化構(gòu)造函數(shù)initialize。乍看之下,這個(gè)方法功能很是簡(jiǎn)單,用處不大,我們完全可以通過(guò)JS提供的new function()的方式來(lái)實(shí)現(xiàn)。但其實(shí)作者的巧妙就是在于此,它將一個(gè)類型(class)的定義跟一個(gè)方法(function)的定義以一種顯示編碼的方式來(lái)作了區(qū)分,很有利于代碼的后期維護(hù),建議使用prototype框架的朋友盡量使用Class.create創(chuàng)建你的自定類型。
最后,我們來(lái)關(guān)注一下Function.prototype.bind與Function.prototype.bindAsEventListener這兩個(gè)方法, 在prototype中許多自定義對(duì)象的封裝, 或者我們自定義的對(duì)象封裝中都大量使用到了這兩個(gè)方法, bind方法主要的功能就是應(yīng)用某一對(duì)象的一個(gè)方法,并用另一個(gè)對(duì)象替換當(dāng)前對(duì)象。bindAsEventListener類似,但它只提供event做為入口參數(shù)。它們主要使用了javascript的apply方法與call方法,不明白可以先看我寫的API Document中的Function類中對(duì)這兩個(gè)方法的定義說(shuō)明與示例。
接著先來(lái)看prototype源代碼中對(duì) Function.prototype.bind 的定義
Function.prototype.bind = function() {
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
}
bind方法return的是一個(gè)function(),在function()函數(shù)內(nèi)部又用_method.apply的方法做一個(gè)轉(zhuǎn)調(diào)用。這種封裝其實(shí)是把a(bǔ)pply的綁定時(shí)即運(yùn)行封裝成調(diào)用時(shí)才綁定后運(yùn)行的方式,這樣的一個(gè)封裝非常有利于我們實(shí)現(xiàn)對(duì)象的組件化實(shí)現(xiàn)。有寫過(guò)的Microsoft的Behavior的朋友知道,Microsoft的支持這個(gè)技術(shù),其實(shí)就是一種把腳本程序完全從HTML頁(yè)面中分離出來(lái)的方法,它對(duì)頁(yè)面元素直接定義行為腳本,在行為腳本中可以以this的引用直接取得元素。而在低版本的瀏覽器不支持apply方法與call方法時(shí)候,我們很難以做到這點(diǎn),常常需要以附加屬性,全局變量等一些方式做中間數(shù)據(jù)保存與交換,耦合度高,聚合度低,舉個(gè)簡(jiǎn)單的例子來(lái)觀察一下:
例子: 我們想讓一個(gè)的BOTTON元素賦于一些行為" 累計(jì)自身被點(diǎn)擊的次數(shù)"
第一種方法是最簡(jiǎn)單的,我們直接給這個(gè)botton元素添加onclick方法來(lái)累加(如下:代碼1)。但這種方式只能應(yīng)用在一些簡(jiǎn)單的前臺(tái)腳本上面,如果我們需要寫一個(gè)具有大量行為的元素時(shí),比如寫一個(gè)Rich Table,我們就希望它最好應(yīng)該是個(gè)組件了。這樣,我我們只要通過(guò)一些簡(jiǎn)單的定義就可以使它自動(dòng)具有一系列的行為。
代碼1: <input type="button" value="100" onclick="this.value = parseInt(this.value) + 1"/>
第二種方法(代碼2: ) 我們通過(guò)一個(gè)普通function定義一個(gè)類型,初始化這個(gè)類型,傳入要產(chǎn)生行為作用的按鈕ID,被指定的按鈕就自動(dòng)具有" 累計(jì)自身被點(diǎn)擊的次數(shù)"的功能了,有效的實(shí)現(xiàn)了HTML元素與腳本代碼的執(zhí)。
代碼2:
<script language="javascript" src="prototype.js"></script>
<SCRIPT language="javascript">
window.onload = function(){
new NonBindClass("btn1");
}
var NonBindClass_Current_Element = null;
function NonBindClass(element){
this.element = $(element);
if(!(this.element || false)){ return null;}
this.element.attachEvent('onclick',printValue);
NonBindClass_Current_Element = this.element;
function printValue(){
//注意,這里的this并不是發(fā)生當(dāng)前動(dòng)作的主體或者當(dāng)前NonBindClass的實(shí)例的引用! 所以我通過(guò)中間變量或全局變量的方式來(lái)取得元素
NonBindClass_Current_Element.value = parseInt(NonBindClass_Current_Element.value) + 1;
}
}
</SCRIPT>
<input id="btn1" type="button" value="100"/>
但是這里我們看到printValue方法中使用NonBindClass_Current_Element這個(gè)全局變量來(lái)取得定義元素的對(duì)象, 但這種全局變量常常讓人無(wú)比的頭痛! 而且, 這時(shí)候如果需要給另一個(gè)按鈕btn3也添加同樣行為,那么全局變量NonBindClass_Current_Element會(huì)被重新指向到btn3,而對(duì)于btn1的點(diǎn)擊事件也會(huì)隨之作用到btn3,使得我們無(wú)法達(dá)到目的。當(dāng)然,我們可以通過(guò)其它的方式來(lái)達(dá)到目的,比如讓printValue方法內(nèi)部可以直接引用到指定的對(duì)象: 一種辦法就是傳入?yún)?shù),但這種方法在做attachEvent方法綁定時(shí)會(huì)直接做調(diào)用到printValue方法! 還有一種辦法 就是使用prototype的bind方法,我們可以給printValue綁定任意的對(duì)象,bind(this)或者bind(this.element)都可以達(dá)到目的,具體請(qǐng)看 代碼3:
代碼3:
<script language="javascript" src="prototype.js"></script>
<SCRIPT language="javascript">
window.onload = function(){
new BindClass("btn1");
new BindClass("btn2");
}
function BindClass(element){
this.element = $(element);
if(!(this.element || false)){ return null;}
this.element.attachEvent('onclick',printValue.bind(this.element));
function printValue(obj){
this.value = parseInt(this.value) + 1;
}
}
</SCRIPT>
<input id="btn1" type="button" value="100"/>
<input id="btn2" type="button" value="200"/>
代碼更加精簡(jiǎn),內(nèi)聚度也高,并且支持給多個(gè)不同的元素賦予同樣行為。
Function.prototype.bindAsEventListener的功能類似,差別在于它只傳入固定的一個(gè)event參數(shù),用于事件后期運(yùn)行時(shí)綁定調(diào)用.