Posted on 2007-09-06 11:31
tearofmoscow 阅读(449)
评论(0) 编辑 收藏
说起OO,我们首先想到的肯定是抽象,继承,多态,重载等一系列的名词。而在JavaScript语言中,因为它是基于对象的(Object-Based),并不是面向对象的(object-oriented),所有它并没有提供这些能力。它仅仅具有一些面向对象的基本的特征而已(比如您可以使用
new 语句来调用一个构造函数)。
但在prototype中通过一些巧妙的方式实现了对象的抽象与继承,
通过使用Object.extend方法,它甚至可以允许我们实现多重继承.
下面我们来看Enumerable的each()方法实现:
第一步:
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方法内部调用到this._each方法,但this._each方法在Enumerable并未做定义,而是将其定义放在Element.ClassNames.prototype中。第二步就是使用Object.extend扩展Element.ClassNames.prototype的内容。整个过程从Java的角度,相当于定义一个抽象类Enumerable,它提供了对枚举对象的各种迭代操作,但迭代内容的方式却通过抽象的方法_each()下放给子类做具体实现,
充分实现了抽象与继承的功能。整个效果还有另一种实现方式是利用Adapter Pattern来实现转调用,但这已经不是抽象与继承的范畴了。
上面例子中我们注意到最重要的一步是Object.extend这个方法的调用了,
看这个方法的源代码我们发现,这仅仅是子类型对父类型的所有property的拷贝,看似简单,但它却帮我们丰富了代码重用的方式,也利于我们用OO的思维观察一个类型了。另外,关于多态在JS中谈是没有意义的,重载JS默认就支持了,只是在调用父类同名方法里需要通过call方法来完成,但在prototype中并未做这种重载的实现,
这里就不再多做讨论了,有兴趣的朋友可以交流。
此外,prototype中还提供了Class这个类型,它只包含一个create方法用于创建一个function对象,并提供默认的初始化构造函数initialize。乍看之下,这个方法功能很是简单,用处不大,我们完全可以通过JS提供的new
function()的方式来实现。但其实作者的巧妙就是在于此,它将一个类型(class)的定义跟一个方法(function)的定义以一种显示编码的方式来作了区分,很有利于代码的后期维护,建议使用prototype框架的朋友尽量使用Class.create创建你的自定类型。
最后,我们来关注一下Function.prototype.bind与Function.prototype.bindAsEventListener这两个方法,
在prototype中许多自定义对象的封装, 或者我们自定义的对象封装中都大量使用到了这两个方法,
bind方法主要的功能就是应用某一对象的一个方法,并用另一个对象替换当前对象。bindAsEventListener类似,但它只提供event做为入口参数。它们主要使用了javascript的apply方法与call方法,不明白可以先看我写的API
Document中的Function类中对这两个方法的定义说明与示例。
接着先来看prototype源代码中对
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的是一个function(),在function()函数内部又用_method.apply的方法做一个转调用。这种封装其实是把apply的绑定时即运行封装成调用时才绑定后运行的方式,这样的一个封装非常有利于我们实现对象的组件化实现。有写过的Microsoft的Behavior的朋友知道,Microsoft的支持这个技术,其实就是一种把脚本程序完全从HTML页面中分离出来的方法,它对页面元素直接定义行为脚本,在行为脚本中可以以this的引用直接取得元素。而在低版本的浏览器不支持apply方法与call方法时候,我们很难以做到这点,常常需要以附加属性,全局变量等一些方式做中间数据保存与交换,耦合度高,聚合度低,举个简单的例子来观察一下:
例子: 我们想让一个的BOTTON元素赋于一些行为"
累计自身被点击的次数"
第一种方法是最简单的,我们直接给这个botton元素添加onclick方法来累加(如下:代码1)。但这种方式只能应用在一些简单的前台脚本上面,如果我们需要写一个具有大量行为的元素时,比如写一个Rich
Table,我们就希望它最好应该是个组件了。这样,我我们只要通过一些简单的定义就可以使它自动具有一系列的行为。
代码1: <input
type="button" value="100" onclick="this.value = parseInt(this.value) +
1"/>
第二种方法(代码2: )
我们通过一个普通function定义一个类型,初始化这个类型,传入要产生行为作用的按钮ID,被指定的按钮就自动具有"
累计自身被点击的次数"的功能了,有效的实现了HTML元素与脚本代码的执。
代码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并不是发生当前动作的主体或者当前NonBindClass的实例的引用!
所以我通过中间变量或全局变量的方式来取得元素
NonBindClass_Current_Element.value =
parseInt(NonBindClass_Current_Element.value) +
1;
}
}
</SCRIPT>
<input id="btn1"
type="button" value="100"/>
但是这里我们看到printValue方法中使用NonBindClass_Current_Element这个全局变量来取得定义元素的对象,
但这种全局变量常常让人无比的头痛! 而且,
这时候如果需要给另一个按钮btn3也添加同样行为,那么全局变量NonBindClass_Current_Element会被重新指向到btn3,而对于btn1的点击事件也会随之作用到btn3,使得我们无法达到目的。当然,我们可以通过其它的方式来达到目的,比如让printValue方法内部可以直接引用到指定的对象:
一种办法就是传入参数,但这种方法在做attachEvent方法绑定时会直接做调用到printValue方法! 还有一种办法
就是使用prototype的bind方法,我们可以给printValue绑定任意的对象,bind(this)或者bind(this.element)都可以达到目的,具体请看
代码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"/>
代码更加精简,内聚度也高,并且支持给多个不同的元素赋予同样行为。
Function.prototype.bindAsEventListener的功能类似,差别在于它只传入固定的一个event参数,用于事件后期运行时绑定调用.