Javascript对于做过Web程序的人不应该是陌生,初期是用来做一些简单的FORM验证,基本上是在玩弄一些技巧性的东西。IE 4.0引入了DHTML,同时为了对抗Netscape的Javascript,提出了自己的脚本语言JScript,除了遵循EMAC的标准之外,同时增加了许多扩展,如下要提到的OOP编程就是其中的一个,为了命且概念,我以下提到的Javascript都是Microsoft Internet Explorer 4.0以上实现的JScript,对于Netscape,我没有做过太多的程序,所以一些的区别也就看出来。
Javascript不是一个支持面向对象的语言,更加算不上一个开发平台,但是Javascript提供了一个非常强大的基于prototype的面向对象调用功能,你可以在你自己需要的地方使用他们。因此,如何使用对象?本文尽可能从Javascript面向对象实现原理出发,解析清楚它的工作模型。在了解这些模型之后,你可以在自己的脚本库中编写一些实现代码,然后在其他地方调用。
Javascript的语法和C++很接近,不过在类实现中没有使用关键字Class,实现继承的时候也没有采用传统的Public或者Implement等等所谓的关键字来标示类的实现。这样的情况下,可能有就有人会问,如何编写Javascript的Class,如何实现继承。我开始也是百思不得其解,后来看了MSDN,才知道采用了prototype来实现,包括继承和重载,也可以通过这个关键字来实现。
Javascript的函数很奇怪,每个都是默认实现了Optional的,即参数都可以可选的,function a(var1,var2,var3),在调用的过程中a(),a(value1),a(value1,value2)等等的调用都是正确的,至少在即使编译部分可以完整通过,至于其它,只是和函数的实现逻辑比较相关了。
以下就JS对于类的实现、继承、重载详细介绍其实现方式。
1。实现
Js类的实现就通过函数直接实现的,每个函数可以直接看成class,如下代码
function ClassTest1(){
...//implement code
}
var a=new ClassTest1
function ClassTest2(var1){
...//implement code
}
var b=new ClassTest("value")
对于类的属性,可以通过两种方式实现
1)this."<Property or Method"的方式实现,在类声明函数中直接给出函数的实现,如 this.Add=new function(strUserName,strPassword)这样的方式调用,编写的方式在Class Function中调用。
2)通过ClassFunction.prototype.[FunctionName]=function(var1,var2...){//todo}这样的方式完成调用。
这两种方式从目标来看是一致的,按照我个人的观点来看,区别的只是在于实现方式,通过this.propertyName的方式来创建,Jscript自动创建了property或者method的入口,不过从程序的角度而言,还是使用prototype的关键字实现比较灵活。
另外Javascript也可以和我们C++中那种嵌套声明的方法来声明,C++实现的方法如下
Public Class ClassName:ParentClass{
Public DataType FunctionName(){
}
Public Class ClassName{
Public DataType FunctionName(){
}
}
}
在Javascript当中,当然不存在class这样的关键字了,所以实现起来有点戏剧性,不过仍然为一个非常巧妙的实现。
function className(){
//Property Implement
this.UserName="blue";
//Method Implement
this.Add=new function(){
}
//Sub Class Implement
function SubClassName(){
this.PropertyName="hi"
}
//sub class method implement
SubClassName.prototype.Change=function{
}
}
//Main Class Method Implement
className.prototype.Delete=function(){
}
如上的代码大致演示了Javascript类中属性和方法的实现,另外有一点比较困惑,整个class中都是public的,没有关键字private之类的可以控制某些方法是否隐藏,那么在我们编写代码实现的规范中,我看国外一些程序员都是使用_functionName这样子为函数命的方法来区分,但是在调用过程中实际还可以调用的。
实现了属性和方法,剩下的就是Event的实现了,我查找了许多资料,包括整个MSDN关于JScript的参考,都没有看到一个很好的模型关于事件实现的,后来参考了一些站点编写HTA(HTML Component,有空我会写一些相关的文章)的实现,借助于比较扭曲(我个人认为)的方法可以大致的实现基于事件驱动的功能。大致的思路是这样子的:
1).将所有的事件定义成属性,只要简单的声明就可以
2).在需要触发事件的代码中判断事件属性是否是一个函数,如果是函数,直接执行函数代码,如果是字符串,那么执行字符串函数,通过eval(str)来执行。
3) .在类的实例当中注册事件函数。
为了简单说明如上的思路,采用timer这样简单的例子来表述如上的所提到的内容,如果只是为了简单的实现timer的功能,Javascript中setInterval函数就可以满足全部的要求,如下的代码只是用来说明Timer的工作原理。
//Class For Timer
function Timer(iInterval){
//if not set the timer interval ,then defalut set to 500ms
this.Interval=iInterval || 500;
this._handleInterval;
this.TimerEvent=null
function Start(){
if(this.Interval!=0){
this._handleInterval=setInterval("TimerCallBack()",this.Interval);
}
}
function Start(){
clearInterval(this._handleInterval);
}
function TimerCallBack(){
if (typeof this.TimerEvent=="function"){
this.TimerEvent();
}
else if(this.TimerEvent!=null && this.TimerEvent.length>0){
eval(this.TimerEvent);
}
}
}
//Code for Instance
var t=new Timer(3);
//------------------------------------//
//1.
t.TimerEvent=function(){
//todo
}
//2.
t.TimerEvent="alert(/"hello/")";
//3.
t.TimerEvent=tTimerCall;
//----------------------------------//
t.Start();
t.Stop();
function tTimerCall(){
}
实际工作代码是在TimerCallBack()上面实现,事件触发作为属性的方式来实现,在应用实例中,代码提供了三种方法去调用事件,不过在事件的回调当中,我还没有想到如何可以带参数,只有才各自的实现当中访问各自需要的属性才能够实现全部的要求。
2。继承。
刚采用了大篇幅的文字去介绍如何实现Javascript的各种实现,也就是从逻辑上完成了一个封装class的实现,从某种意义上来说,class的实现是真正脚本编程中使用最多的部分,不过如果只是要完成如上的功能,使用VBScript来编写更能更加清晰,毕竟VBscript提供了class关键字,同时提供了public 和private这两个关键字,可以清晰的将公共和私有对象分离,至于事件的实现,也可以采用类似Javascript实现的思路,只是对于函数的引用需要采用GetRef这个函数,具体的用法可以参考scripting reference,MSDN里头也有详细的介绍,而Javascript强大至于在于如下要说的了,虽然具体的东西可能不多。
如上所言,我们已经完成了一个基本的类实现Timer,现在要做的是重新编写这个类,我们简单的只是想在这个类之中加入一个方法,提供当前的系统时间,方法的名称为getSystemDate,显然如果全部重新编写,那就失去了我这里说的意义了。先看看如下的实现。
function NewTimer(iInterval){
//call super
this.base=Timer;
this.base(iInterval);
}
NewTimer.prototype=new Timer;
NewTimer.prototype.getSystemDate=function(){
var dt=new Date();
return dt.getYear()+"-"+dt.getMonth()+"-"+dt.getDay();
}
上述代码实现了NewTimer类,从Timer继承,Javascript没有使用“:”或者java的public那样类似的关键字,只是通过newclassname.prototype=new baseclass这样的方法来完成,同时NewTimer实现了getSystemDate的方法,在NewTimer的初始化函数中,我使用了this.base=Timer,是为了引用父类的实现,不过在对于父类其他实现函数的调用,到现在我没有找到一个确定的方法,是否通过this.base.start()那样来调用还是其他的,如果有谁比较清楚的,麻烦告诉我,另外在netscape的站点上,我查到有一个特殊的"__proto__"的属性好像是对于父类的直接引用,不过具体的我也没有尝试过,在msdn中也没有看到对于__proto__的支持。
3。重载
或许这个是OOP编程中比较复杂的地方了,在Javascript的实现中有点无奈,也就是通过prototype的方式来完成的,不过因为我不清楚如何调用父类的实现函数,那么在重载中只能够重新编写所有的实现了,另外就是在实现中实例化一个父类,然后通过调用它来返回需要的东西。
Javascript中所有的对象都是从Object继承下来的,object提供了toString()的方法,也就是说如果调用alert(objInstance)这样的过程,实际上是调用了alert(objInstance.toString())的方法,如果没有编写实现,object默认的toString()都是"object object"这样子的,在许多地方需要重载这个函数的,比如Timer,如果我们希望var ins=new Timer(5);alert(ins)调用得到的是interval的值5,那么就需要重新编写toString()方法了
Timer.prototype.toString=function(){ return this.Interval};
以上代码实现之后alert(ins)得到的就是5了。
评“面向对象的JavaScript编程”一文
作者没有看过netscape的文档,也没有看过ECMAScript(ECMA-262)规范,仅仅看msdn是不能真正懂得javascript的。
下面我来指导一下吧。 :)
> 面向对象的JavaScript编程
JavaScript虽然可以认为是一个对象语言,但是与大家熟悉的c++,java不同。主要的区别在于:
1. js是基于对象的语言,而不是严格的面向对象语言。
2. js的对象是基于原型的。什么是prototype?可以去看看《设计模式》中的prototype模式。
3. js的function是一等公民,js不区分class和object。
> Javascript对于做过Web程序的人不应该是陌生,初期是用来做一些简单的FORM验证,基本上是在玩弄一些技巧性的东西。IE 4.0引入了DHTML,同时为了对抗Netscape的Javascript,提出了自己的脚本语言JScript,除了遵循EMAC的标准之外,同时增加了许多扩展,如下要提到的OOP编程就是其中的一个,为了命且概念,我以下提到的Javascript都是Microsoft Internet Explorer 4.0以上实现的JScript,对于Netscape,我没有做过太多的程序,所以一些的区别也就看出来。
很多人都仅仅玩弄js技巧,其实是半瓶水的表现。在浏览器中过分卖弄js,却不懂得写noscript标签,这是典型的还没有明白web之道的表现。DHTML这种厂商对抗的东西已经没有什么好说的了,大家自己去看DOM标准吧。顺带说一下,netscape有js的guide和reference,建议真正想深入js的人至少要去看一遍,msdn上对js语言的解说虽然一个版本比一个版本多了,但是始终是不如创造语言的公司说得清楚。或者推荐大家看奥莱理的《js权威指南》,我随手翻过,是有史以来最好的js书之一。
> Javascript不是一个支持面向对象的语言,更加算不上一个开发平台,但是Javascript提供了一个非常强大的基于prototype的面向对象调用功能,你可以在你自己需要的地方使用他们。因此,如何使用对象?本文尽可能从Javascript面向对象实现原理出发,解析清楚它的工作模型。在了解这些模型之后,你可以在自己的脚本库中编写一些实现代码,然后在其他地方调用。
作者这里说的对的,js本身是脚本语言,而且是依赖于host的语言(根据ecma规范的说法),因此很多人关于js如何如何的说法都是错误的,因为他们说的是dhtml或者dom,总之是浏览器环境,而不是js语言的特性。ms的dhtml可以用于js和vbs,DOM是语言独立的。js本身是单纯的语言,虽然主要以浏览器环境为设计目标,但并不影响他在其它环境中发挥作用。例如可以作为服务器端脚本,比如asp(虽然多数用vbs),或者jsp的一种脚本语言(注意js不是jsp,jsp通常使用java作为脚本语言,但是也可以用其他脚本语言)。“基于prototype的面向对象调用功能”这句话就有问题了。prototype不是调用功能,而是语言的机制。而且“面向对象”的提法也不完全准确。从术语使用上讲,面向对象一般指c++,java,smalltalk等,js只被叫做“基于对象”的,当然我觉得这也不是很重要。
> Javascript的语法和C++很接近,不过在类实现中没有使用关键字Class,实现继承的时候也没有采用传统的Public或者Implement等等所谓的关键字来标示类的实现。这样的情况下,可能有就有人会问,如何编写Javascript的Class,如何实现继承。我开始也是百思不得其解,后来看了MSDN,才知道采用了prototype来实现,包括继承和重载,也可以通过这个关键字来实现。
js的语法是c一路的(java,c#也是)。不仅是没有关键字class那样,严格来说,js没有类的(当然也没有继承一说了)。但是通俗来说,可以认为,js中有类似“类”的东西。顺带说c++不是“完全”的对象语言,因为缺乏“类对象”,呵呵(不过这是语言的不同设计,未必是缺点)。至于imp,那是接口了,许多语言没有明显接口而用多重继承、虚类、抽象方法来模拟,比如c++。prototype并不只是一个关键字,而是js的特质之一。如何继承,虽然你可以从msdn摸索,不过是事倍功半,还是再劝你直接看netscape的guide,或者mozilla上也有。
> Javascript的函数很奇怪,每个都是默认实现了Optional的,即参数都可以可选的,function a(var1,var2,var3),在调用的过程中a(),a(value1),a(value1,value2)等等的调用都是正确的,至少在即使编译部分可以完整通过,至于其它,只是和函数的实现逻辑比较相关了。
这没有什么奇怪的,只是js的许多特性(动态类型,没有同名函数……)的结果。补充一个,在function之内,可以用arguments像数组一样操作参数。
> 以下就JS对于类的实现、继承、重载详细介绍其实现方式。
> 1。实现
> Js类的实现就通过函数直接实现的,每个函数可以直接看成class,如下代码
应该这样说,js没有类,但是js中,一切都是对象,包括函数。因此类可以通过做一个构造函数(注意,构造函数是来构造对象而不是构造类的)来模拟。
> 对于类的属性,可以通过两种方式实现
> 2)通过ClassFunction.prototype.[FunctionName]=function(var1,var2...){//todo}这样的方式完成调用。
先纠正用词,这里不是调用而是赋值。
> 这两种方式从目标来看是一致的,按照我个人的观点来看,区别的只是在于实现方式,通过this.propertyName的方式来创建,Jscript自动创建了property或者method的入口,不过从程序的角度而言,还是使用prototype的关键字实现比较灵活。
这两种方法效果基本一致,但是还是有比较重要的区别,即前者是在构造函数里赋值,后者则是从外部通过原型对象赋值。如果有比较复杂的“类层次”,你就会发现两者的不同,还有顺序问题了 :) 后者是更灵活,不过要知道js是完全动态的语言,你甚至可以动态改变constructor,呵呵。
> 另外Javascript也可以和我们C++中那种嵌套声明的方法来声明,C++实现的方法如下
> 在Javascript当中,当然不存在class这样的关键字了,所以实现起来有点戏剧性,不过仍然为一个非常巧妙的实现。
> function className(){
> //Property Implement
> this.UserName="blue";
> //Method Implement
> this.Add=new function(){
^^^
作者一个小笔误,这个new是多余的。
> }
> //Sub Class Implement
> function SubClassName(){
> this.PropertyName="hi"
> }
>
顺带说一下,作者过分使用了object.prototype.prop = value 的方法,这其实并不好。因为这破坏了“类”的结构。除了直接赋值原型对象来模拟继承,如无必要,不应使用。
> 如上的代码大致演示了Javascript类中属性和方法的实现,另外有一点比较困惑,整个class中都是public的,没有关键字private之类的可以控制某些方法是否隐藏,那么在我们编写代码实现的规范中,我看国外一些程序员都是使用_functionName这样子为函数命的方法来区分,但是在调用过程中实际还可以调用的。
public之类的,只是为了增加语言的可靠性,对于脚本语言来说,并不是必须。
> 实现了属性和方法,剩下的就是Event的实现了,我查找了许多资料,包括整个MSDN关于JScript的参考,都没有看到一个很好的模型关于事件实现的,后来参考了一些站点编写HTA(HTML Component,有空我会写一些相关的文章)的实现,借助于比较扭曲(我个人认为)的方法可以大致的实现基于事件驱动的功能。大致的思路是这样子的:
> 1).将所有的事件定义成属性,只要简单的声明就可以
> 2).在需要触发事件的代码中判断事件属性是否是一个函数,如果是函数,直接执行函数代码,如果是字符串,那么执行字符串函数,通过eval(str)来执行。
> 3) .在类的实例当中注册事件函数。
这一部分不作评论,自己去看DOM 2的事件模型。
> 2。继承。
> 刚采用了大篇幅的文字去介绍如何实现Javascript的各种实现,也就是从逻辑上完成了一个封装class的实现,从某种意义上来说,class的实现是真正脚本编程中使用最多的部分,不过如果只是要完成如上的功能,使用VBScript来编写更能更加清晰,毕竟VBscript提供了class关键字,同时提供了public 和private这两个关键字,可以清晰的将公共和私有对象分离,至于事件的实现,也可以采用类似Javascript实现的思路,只是对于函数的引用需要采用GetRef这个函数,具体的用法可以参考scripting reference,MSDN里头也有详细的介绍,而Javascript强大至于在于如下要说的了,虽然具体的东西可能不多。
关于vbs我只说一句:vbs是M$牌的垃圾。
> 上述代码实现了NewTimer类,从Timer继承,Javascript没有使用“:”或者java的public那样类似的关键字,只是通过newclassname.prototype=new baseclass这样的方法来完成,同时NewTimer实现了getSystemDate的方法,在NewTimer的初始化函数中,我使用了this.base=Timer,是为了引用父类的实现,不过在对于父类其他实现函数的调用,到现在我没有找到一个确定的方法,是否通过this.base.start()那样来调用还是其他的,如果有谁比较清楚的,麻烦告诉我,另外在netscape的站点上,我查到有一个特殊的"__proto__"的属性好像是对于父类的直接引用,不过具体的我也没有尝试过,在msdn中也没有看到对于__proto__的支持。
之所以不是像其他语言那样继承,那是因为这根本不是“真正”的继承,因为js根本没有“真正”的类,呵呵。prototype的实质就是对原型对象的一个引用。至于调用“父类”方法,其实很简单,在调用了“父类”的构造函数之后直接调用就是了。至于为什么用this.base=parent,其实想明白一件事情就很简单了。parent实际上并不是类,而是构造函数。在“子类”构造函数内部,当时还没有原型存在(prototype是外部动态赋值的,不是静态信息)。所以必须手动的获取“父类”构造函数,运行一下。
也许有人要问,那既然子类直接运行父类构造函数,干吗还要原型?把所有东西写在构造函数不就结了?嘿嘿,想想作者的“第二种方法”。prototype有个特性,给原型增加一个属性(包括方法),所有继承这个原型的对象都会自动添加这个属性。因此,如果你要用这个特性,还是需要原型的。至少你可以用instanceOf操作符来判断变量的类型。
__proto__可以被检测和回溯,从而形成一个原型链,这在比较复杂的“类结构”里是有用的,你可以用它来写自己的instanceOf。不过这不是ecma规范要求的,也应该仅仅在语言内部使用(你真的需要在子类里去动态改变父类?)。
> 3。重载
> 或许这个是OOP编程中比较复杂的地方了,在Javascript的实现中有点无奈,也就是通过prototype的方式来完成的,不过因为我不清楚如何调用父类的实现函数,那么在重载中只能够重新编写所有的实现了,另外就是在实现中实例化一个父类,然后通过调用它来返回需要的东西。
这个无奈只是作者杞人忧天 :)
实例化一个父类对象没有什么大不了的,不要拘泥于“正统”的OO观念。想想看,在原型继承的时候,你实际上不正是实例化一个父类对象,然后赋值给prototype吗?只是这个并不是一个真正需要的实例,而只是一个“模子”,所以也许你可以不用传给构造函数任何参数。调用“父类”的方法,也并不必然需要实例化一个父类对象。回忆一下调用父类构造函数的过程。在调用之后,你就已经获得了所有的通过父类构造函数初始化的属性和方法。要干什么随便你了。
> Javascript中所有的对象都是从Object继承下来的,object提供了toString()的方法,也就是说如果调用alert(objInstance)这样的过程,实际上是调用了alert(objInstance.toString())的方法,如果没有编写实现,object默认的toString()都是"object object"这样子的,在许多地方需要重载这个函数的,比如Timer,如果我们希望var ins=new Timer(5);alert(ins)调用得到的是interval的值5,那么就需要重新编写toString()方法了
除了toString,你也可以重写valueOf用来给你的对象返回一个原生类型的值,呵呵。
> 长篇累牍的说了一堆废话,终于说玩了大致的想法,其实语言只是一个实现工具,重要的在于设计的思想,不妨可以考虑一下,在BITI内部开发一个OpenSource的Project,如果是基于javascript的模型来建立开发平台库,我希望有人可以参与。通过javascript建立一系列基于Web UI的控件,目前我在开发过程中也是立足于上述的想法。另外,附上我去年写的类似HotMail的按钮那样的class源程序,暂时还没有使用Image Preload,希望有人可以帮我修改一下,如果需要可以运行的版本,给我发送Email:liuruhong@263.net。另外有空我会写基于Javascript的组件编程和多媒体编程部分,再下来就是XML方面了,希望大家共同进步。
语言是工具,有不同的应用目标。设计思想当然可以共通,特别是对象语言。你大可以用js快速开发一个原型(一般意义上的原型,不是前面的技术术语),然后用java, c++作最终成品。至于用js写webUI,我要稍微泼一点点冷水,不必太过在意。webUI的问题主要不在于js,而在于浏览器本身和其他技术标准。比如mozilla的UI用XUL语言来写的。而且许多人认为XBL和HTC(都是用js来封装web控件)等不是很好的方向(虽然我一度挺喜欢这两种技术),因为没有很好的正交分解,把样式、js等都混淆了。未来的方向还是要靠w3c这个标准化组织给出。所以用js也不必写很复杂的类结构(毕竟脚本语言是要给人们轻松的),更多精力花在DOM和兼容性上吧。
最后说一下,netscape还在继续开发js 2.0(好几年了),ecma也跟着同时作edition 4(M$就不清楚其动向,而且其jscript还没有完全实现edition 3),这个版本里好像加入了class,public等关键字,可能会有很大的变化。有兴趣的人可以去看:http://www.mozilla.org/js/language/js20/index.html
评论
唉,这个HAX很喜欢挑别人的问题,其实通篇上下没说到一点实际性的有意义的东西,断章取义的挑剔别人的文字中的错误,自已说话想当然的犯更多的错误
象这些
““基于prototype的面向对象调用功能”这句话就有问题了。prototype不是调用功能,而是语言的机制。而且“面向对象”的提法也不完全准确。从术语使用上讲,面向对象一般指c++,java,smalltalk等,js只被叫做“基于对象”的,当然我觉得这也不是很重要。
象你这样一个字一个字的断章取义,你的语病更多,什么“而且“面向对象”的提法也不完全准确。从术语使用上讲,面向对象一般指c++,java,smalltalk等”,面向对象就是c++吗?c++是一种语言,而面向对象是一种设计思想,正如你说prototype不能调用一样,你说是不是有有点无理取闹的
好象你一直在反复的强调你比liuruhong强的多,水平好的多,可是看你的评论都不知道你东一句西一句在说什么
先引用别人的,然后来句“其实这是不对的”,然后认真看你的解释,看了半天全是废话
反而是liuruhong写的几篇文章认真看过,很有水平,钻研精神很可贵。
HAX的什么js其实没有类,都是对象,知道什么叫对象吗?简直是胡说作八道
类是一种定义,而对象是实例化,js里function就是类,可是实例化成不同的对象,而不是象HAX说的都是对象的引用,所谓对象的引用是没有多个实例存在的,只是多个指针指向同一个对象,他们的属性与内存都是想同的,所有HAX根本就没有理解什么是面向对象
理解了这个,就明白他们实际上并没有区别,但是在产生的先后上有区别
试试下面这个
function test()
{
test.a = function () { alert("test.a "); };
test.prototype.a = function () { alert("test.prototype.a"); };
this.a = function () { alert("this.a"); };
}
t=new test();
t.a();
猜猜结果是什么?
test()首先创建了test.a 调用类名test创建所有的方法与属性
然后他发现test.prototype.a 这是调用test.prototype修改类定义,就覆盖了原来的a
最后在实例化成对象的时候,他发现this.a 于是又覆盖了原来的a
所以他的结果是什么应当明白了,很简单,他们都是this.a,没有HAX夸夸其谈的区别,HAX也没有说出他的本质
另外,如果
function test()
{
}
test.prototype.a = function () { alert("test.prototype.a"); };
他的优先权是低于在类内定义的同名函数,会被类内的同名函数覆盖,不管类内的同名函数是如何定义的
所以liuruhong文中会有那个结果调用基类的函数,因为那个实例化在后面覆盖了前面的,如果改成在test()函数体内结果就不一样了
还有HAX轻蔑的认为liuruhong 文中的new是多余的是错误的,类定义中用上new的子函数,会在new实例化对象的时候全部执行一次,反之则不会
基本上HAX大多是错的,而且很多的废话
虽然一年前的文章了,今天偶尔看到,还是其人的态度很令人恶心,忍不住写上一通
再论面向对象的Javascript编程
作者:蓝翼
联系方式: liuruhong@263.net
关键字:Javascript OOP 面向对象 Jscript 原型 prototype
提要:
在原先《面向对象的JavaScript编程》中,笔者提出了通过Javascript(确切意义上面来说是Microsoft的Jscript)来实现OOP的思想,原文中因为笔者知识的局限,存在一些没有理清楚的思路,感谢一些网友提供的评论,结合这段时间的实际工作,笔者修正了原文中的一些想法.
因为个人知识水平有限的原因,以下所提到的JavaScript仅仅止于Microsoft 的Jscript,至于和NetScape之间的一些不同,也仅止于笔者所知道的范围。在原文中笔者提出了面向对象的JavaScript的概念,不是希望在卖弄Script的技巧,只是觉得在开发的过程中或多或少的会用到,因此从个人的角度提出一些相对可行性的建议。
原文对于Javascript提出了实现,封装,继承,重载,事件等等各个概念,因为个人理解的偏差,原文中有许多地方不甚贴切之处,因此本文只是对于原文的一些个人看法的一些修正。
Javascript从OOP的角度来说,应该不是一门纯OOP的语言,更加准确来说,是一门Object-Based的脚本语言,因此原文提到的所谓类的概念,只是从传统OOP语言的角度去描述的,而所谓的类实现,应该是一种原型实现方式,因为在整个实现的过程中prototype是一个最重要的体现。
理论上来说,所有的function都是object,因此类的创建可以完全基于Object来实现的,原文采用function只是为了更好的实现一些功能,以下就实现,封装,继承,重载,事件这5个概念作一些具体的阐述。
1. 实现。
Javascript的function是一等公民(hax原语),因此采用function来实现是最好的方式,至于采用如下两段代码来实现这个就是根据个人编程的习惯来决定,而一些实现的偏差我会在下文详细的描述。
代码1
function MyObject(sName){
this.name=sName;
this.MessageBox=function(){
alert("名字"+ this.name);
}
this.ChangeName=ChangeName;
function ChangeName(){
this.name="改变" + this.name;
}
}
代码2
function MyObject(sName){
this.name=sName;
}
MyObject.prototype.MessageBox=function(){
alert("名字"+ this.name);
}
MyObject.prototype.ChangeName=function(){
this.name="改变" + this.name;
}
采用标准的类实现结构和原型实现方式,在使用来说是没有区别的,如果从传统的类设计如(C++,smalltalk那样的语言)来说,应该采用第一种方案,但是就我个人而言,我建议使用第二种方案,毕竟在开发的过程中,那样的编写方式和Javascript的编写方式比较接近,基本立足于function的实现。
2.封装
既然引入了对象的概念,必然需要考虑到封装,C++这样的语言中有提出private ,protected,public的概念,在Javascript中,没有对应protected的概念,但是在private和public能够做到隐含的实现,另外如果需要大量使用到私有变量,实现方式中采用方案一可能更加合适一点,如下代码所示。
function MyObject(sName){
var mVar=1;
this.name=sName;
this.MessageBox=function(){
alert("名字"+ this.name);
mVar=2;
}
this.ChangeName=ChangeName;
function ChangeName(){
this.name="改变" + this.name;
}
this.GetVar=function(){
return mVar;
}
}
mVar是一个私有变量,name是一个共有变量,在MyObject内部实现的函数,可以访问mVar变量,而name则可以通过this.name的方式在任何实现的地方访问,对于内部函数的处理,ChangeName是不可以被直接访问的,但是可以通过Object.ChangeName()访问,这些概念在hax的评论中有几个网友讲述的比较详细.
简单而言,Javascript提供了对象中的private和public,但是并不提供显式声明,在类函数(姑且让我如此称呼)中定义的函数和变量都是私有变量,通过this.methodName或者this.varName定义的都是public实现的,通过object.prototype.name实现的也是public,在类函数中的私有函数可以访问那些私有变量和函数的,至于通过object.prototype.name那样实现的方法是否可以访问私有变量,我没有做过,如果谁做过,麻烦告诉我。
在VBS中提出了class的概念,如下的代码能够完整地体现封装的风格。
Class MyClass
Private vLoginID
Public UserName
Public Property Get LoginID
LoginID=vLoginID
End Propertyp
Public Property Let LoginID(vValue)
vLoginID=vValue
End Property
End Class
在Javascript中没有显式所谓属性概念,这点上面VBS的封装确实比较干净,JavaScript
中没有这样的概念,但是可以采用和Java类似的设计方法,如Get_PropertyName,Set_PropertyName这样的方法来做。我个人建议就采用函数来实现,如果如果带参数,则表示进行set操作,如果不带参数,可以认为是get操作。简单而言采用如下的代码风格。
Object.prototype.UserName=function(vValue){
If(vValue){
//todo get
}
Else{
Return value;
}
}
以上提及的只是我个人实现的观点。
3.继承
原文中提到通过设置原型的方式可以实现继承,就是通过SubObject.prototype=new BaseObject这样的方式来实现,至于对应的构造函数,在原文中采用了
This.base=parentclass;
This.base();
这样的方式实际是调用父类的构造函数来完成的,完成prototype(原型)设置之后,通过SubObject实例化的对象就可以访问父类的所有方法。在实际开发过程中碰到如下的问题,本来考虑到实现的时候通过方案一和方案二是没有任何区别的,可是如下的代码却有点奇怪。
function son(sName){
this.base=MyObject;
this.base(sName);
}
son.prototype.MessageBox=function(){
//this.base.MessageBox();
alert("子类调用");
}
son.prototype.hi=function(){
alert("kk");
}
var o=new son("hello");
o.MessageBox();
最后的执行结果是上文MyObject.MessageBox的方法,son.prototype.MessageBox这个函数却不执行,不清楚是否是我的代码还有问题,在继承方面,我发现通过this.method=functionName和object.prototype.methodName实现似乎不是完全一样的,对于这个方面如果有谁比较理解,麻烦告诉我。
在刚才的代码中不使用prototype就可以完成继承,这里采用的方法应该是构造函数初始化,因此Object.MessageBox等等一些函数都完全初始化,而如果没有写SubObject.prototype=BaseObject,那么BaseObject.prototype.methodName来实现的一些方法就无法实现。
4.重载
在原文中我提到使用Object.prototype.MethodName的方式来重写方法,但是同时保留了一个问题,就是如何调用父类的方法,上文提到的this.base=MyObject,this.base()这样只能够实现构造函数的实现的一些方法,也就是父类在函数体内本身定义的一些方法或者属性,通过原型prototype实现的就无法实现,在采用SubObject.prototype=new BaseObject那样的方式,也会出现一些上文提到的一些问题。
在Jscript 5.5以上版本,有一个call函数,通过这个函数我们可以完整的实现对于父类的方法调用,就如同Java里头的super,也就可以实现重载。如果读者的客户端系统是IE6或者WinME,就已经是Jscript5.5以上版本,至于XP或者2003我就不用多说了,Jscript引擎版本比较低的我建议下载高版本的。
BaseObject
-------------------
function BaseObject(){
this.msg=null
}
BaseObject.prototyp.Message(msg){
this.msg=msg;
alert(this.msg);
this.msg="change"+this.msg;
}
SubObject
function SubObject(){
BaseObject.call(this);
}
SubObject.prototype=new BaseObject;
SubObject.prototype.Message=function(msg){
BaseObject.prototype.Message.call(this,msg);
alert(this.msg);
}
调用代码
Var v= new SubObject();
v.Message(“hello”);
对于父类的方法通过call去调用,call(this,args)表示当前实例调用,在SubObject中,BaseObject.call(this)调用父类的方法,在重载Message函数的时候,为了维系原有的功能,首先调用父类的Message方法,然后才编写自己的实现代码。
从代码中大家可以明显看到是使用BaseObject.prototype.MethodName.call(this,args)这样的方法调用的,因为使用到了call函数,所以需要比较高版本的支持。
以上我讨论的是针对Jscript的实现,至于在别的引擎如NetScape或者Flash Script中,可以通过__proto__这个来返回父类的原型,因此调用上就简单许多,this._proto__相当于Java里头的super,在这里可以直接调用。
在低版本的Jscript引擎中我不知道是否可以通过instanceOf或者别的函数来共同实现,这也希望大家来共同探讨。
5.事件
本来,在OOP编程中,我不应该讨论如此的问题,但是在实际的应用中可能需要使用到类似事件的形式,因此在原文中,基本是采用了类似C++中虚函数的方法,不过在定义的时候只是使用了一个普通变量的方法。
如果类本身是和dom绑定的,对于HTML可以使用fireEvent函数来引发一些事件,不过这些应该已经属于DHTML讨论的东西了。
Javascript本身上来说没有所谓真正意义上面的面向对象,原文和本文提及的也只是正对实际开发工作中的一些逻辑实现,在浏览器内部确实不是适合大量的使用OOP的东西,但是对于Jscript Library开发人员来说,面向对象确实能够提供不少的好处,笔者写本文的目的也是希望能够对于Jscript Library的开发人员有说帮助,至少能够作为一个参考的资料。
因为本身水平有限,在一些的理解上面可能有些偏差,也希望有人能够给我指出来。
参考:
1. 面向对象的JavaScript编程(liuruhong原作)
http://www.youkuaiyun.com/develop/read_article.asp?id=19401
2. 评“面向对象的JavaScript编程”(hax原作)
http://www.youkuaiyun.com/develop/Read_Article.asp?Id=19485