随着AJAX的大行其道,对于JavaScript的面向对象编程的需求就显得极为迫切,因为大量的客户应用代码需要在客户端处理,为了增加客户端代码的开发效率,我们不得不使用面向对象的编程方式来封装我们的代码,用来构建我们自己的开发框架。
虽然JavaScript本身就是构建在对象的基础上的一个语言。我们在使用它的时候,你会发觉对象无处不在,其中包含其自身的内部对象原形Array,Boolean,Date,Math,Nummber,Object,String,Function,RegExp,Global,Enumerator,Error,以及浏览器中的DOM对象等等……。但是Javascript本身并不提供class声明语法来创建对象。虽然JScript.NET版本已经提供了class语句来声明对象,和private,public等常见的封装对象的语句,但是要求是客户端必须拥有.NET Framework的支持,总所周知.NET平台现在只局限于企业级应用,以及服务器端的应用。还并没有普及到普通PC上来,所以我们使用JScript.NET版本开发客户端应用就有点的得不偿失了。况且我们为了满足客户的需求还需要兼容其它的浏览器,所以JScript.NET在这里我们暂且不提。
那我们怎么创建自己的对象呢?
网上流传了很多创建对象的方法,我在这里举例说明,并且分析一下为什么可以这么写。
首先是基于Object原形实例的对象:
//示例
var MyObj = new Object();
MyObj.Msg = "My Message"
MyObj.ShowMsg = function()//创建一个Msg的方法用以在屏幕上显示一个对话框的内容
{
window.alert(this.Msg)
}
MyObj.Msg()//调用该方法
这时我们发现基于原形实例的对象,我们虽然可以创建出一个对象并且可以直接用.的方法添加新的属性,但是我们发现我们不能创建私有成员,并且不能拥有继承,所以这种方法创建对象并不是我们所需要的,我们pass他
基于Array原形实例的对象:
//示例
var MyObj = new Array();
MyObj.Msg = "My Message"
MyObj.ShowMsg = function()
{
window.alert(this.Msg)
}
MyObj.Msg()//调用该方法
我们发现它和基于Object原形实例的对象是一样的,所以也不用考虑。
但他的另一种写法,我们可以在这里提一下
var MyObj = {
Msg:"My Message",//注意这后面的","号,因为我们使用数组直接创建的对象所以他遵循数组的书写规范
ShowMsg:function()
{
window.alert(this.Msg)
}
}
其实说到这里,我们不难看出,其实在JavaScript里对象就是用关联数组来实现的,又因为它是一个弱类型的语言,它的值可以是任何类型,当然也包含函数类型。所以我们可以用这种方式来实现一个对象实例。
基于function的封装类:
现在我们谈到的类的封装,因为使用function创建的类(我们暂且叫它类,其实它并不是一个类,只是一个Function原形的一个实例,仅此而已),我们可以在创建新的实例的时候自动的添加其成员。至于他在解释器内部是如何创建的,我们稍候再谈。先来看一个例子。
function MyObj()
{
this.Msg = "My Message"
this.ShowMsg = function()
{
window.alert(this.Msg)
}
}
当然我们也可以把它的成员写在外面,如下:
function MyObj()
{
this.Msg = "My Message"
}
MyObj.prototype.ShowMsg = function()
{
window.alert(this.Msg)
}
在这里我们需要理解一个概念,这两个例子只是创建了一个Function类型的一个实例MyObj,第二种方法是在外部给这个实例添加一个功能属性,这个功能属性是一个函数。
之后我们就可以用这个函数创建我们自己的对象了,
例如:
var Obj = new MyObj()
Obj.ShowMsg()
为什么这样写?在回答这个问题之前,我们需要掌握两个概念:
一:this是什么?
this在官方的文档里描述为当前对象,何谓当前对象?就是当前这个函数或变量是属于那个对象的。一般在我们的所有函数以及变量都是定义在window对象里的,所以如果你直接在<script>标签里调用this是恒等于window对象的。但我们不能用this调用我们在<script>标签内定义的函数和变量,这个我们稍候再谈。
二:new到底干了些什么?
new在官方的文档里描述为使用构造函数来创建对象,并返回其对象。在这里,我们的构造函数是MyObj(),new方法在使用的时候创建一个空的对象,并执行我们后面所跟的函数,在这里我们的MyObj()函数在运行的时候分别给this添加了两个属性Msg,ShowMsg并且将MyObj的prototype方法复制给this,这里的this引用的就是我们new语句创建的对象。其本质和给Object原形创建的实例添加属性的方法是一样的。
我们来稍微改写一下,也许您就更容易理解上面这种写法都作了些什么。
示例:
var Obj = new Object()
function MyObj()
{
Obj.Msg = "My Message"
Obj.ShowMsg = function()
{
window.alert(this.Msg)
}
}
MyObj()
Obj.ShowMsg()
也许这样更容易理解一些
好!到现在我们已经用Javascript所拥有的语言特性模拟出来了一个简单的类,但还是没有满足一个类私有属性封装的需求,现在我们再来看一个例子
function MyObj()
{
var MsgContent = "Apple"
this.Msg = "LeoWoo"
this.ShowMsg = function()
{
window.alert(this.Msg+"'s "+MsgContent)
}
}
var Obj = new MyObj()
Obj.ShowMsg()
我们惊奇的发现MyObj函数里定义的MsgContent竟然可以访问!!这在我们以前在其他语言里的函数是不可能的,因为在我们前面所分析过的,MyObj函数只在创建实例的时候运行过一次,MsgContent因该在MyObj函数运行过后就自动销毁了啊?难道他类似于其他语言的局部静态变量?答案是肯定的,这个变量在MyObj函数运行过后还继续存在,那么它确实是一个静态变量。但是现在问题又来了,我们前面分析过,用MyObj函数在创建Obj时ShowMsg属性被添加到了Obj里,他何来的权限访问一个函数里定义的变量呢?这里我们需要注意到几点。试想一下,如果这个MsgContent变量和ShowMsg函数没有定义到函数体里,那ShowMsg可以访问MsgContent那就理所应当了。
例如这样:
var MsgContent = "Apple"
ShowMsg = function()
{
window.alert(MsgContent)
}
顺便说一下,在Javascript里函数定义有多种写法
如:函数名 = function(){}或者function 函数名(){}这两者是一样的
好了,现在我们明白为什么Obj.ShowMsg可以有权限调用MyObj函数里定义的局部变量MsgContent了吧,因为ShowMsg所引用的函数定义是在MyObj函数里,并且和MsgContent是平级的,只不过在MyObj函数运行的时候把这个函数引用添加到了new语句创建的新对象里,并且引用赋值给了Obj变量。
当然,如果你用MyObj.prototype.ShowMsg方法在外部添加的话,你就没有权限访问MyObj函数里定义的局部变量MsgContent的。
现在我们解决了怎样用一个函数创建一个对象,并且可以设置私有属性和公有属性,那我们是不是还缺个继承?
我们来看这个例子:
function NewObj()
{
this.Base = MyObj
this.Base()
this.SetMsg = function(PMsg)
{
this.Msg = PMsg
}
}
var Obj = new NewObj()
Obj.SetMsg("六月初六")
Obj.ShowMsg()
这个例子我们结合刚才的那个function MyObj()例子,我们发现NewObj()创建的对象包含MyObj()里需要创建的属性,这是为什么呢?从刚才我们分析的以前的那几个例子当中我们就不难理解为什么这样写就可以轻松实现继承了。因为在NewObj函数运行时,调用了MyObj函数,所以两个函数同时将新对象的属性赋值,这样就模拟了类的继承。
明眼人一定发现,同样是运行,我为什么不直接写MyObj()来运行,而要写成this.Base = MyObj;this.Base()的方式来运行呢?细想一下,MyObj函数可是定义在<script>标签里的,NewObj函数调用它的时候,MyObj函数里的this可不是新建的对象,而是window对象,如果你这样写,就等于是在window对象里添加属性了,而我们先在NewObj函数运行时给新对象this添加一个属性,并且将MyObj函数引用赋值给他,那这时MyObj是否就成了新对象里的一个方法了?我们运行新对象里的方法,那其this就等于当前对象,这样我们的继承也就模拟出来了。
当然我们也可以模拟继承多个类,在这我就不多说了。
至于重载,也就是给对象属性从新赋一个值,我在这就不在做讨论分析了。
在我们给对象属性赋值的时候需要运行一些代码怎么办?
在其他语言里如VB我们可以用如下代码:
Class MyObj
Public cMsg
Public Property Get Msg
Msg = cMsg
End Property
Public Property Let Msg(PMsg)
vMsg = PMsg
End Property
End Class
C++可以用运算符重载
C#可以用get,set关键词等等……
那我们JavaScript怎么做到呢?我们可以用类似Java分别做set_Msg和get_Msg两个函数来做,当然也我们也可以模拟。
在我们的Function里有一个属性argument可以帮助我们实现这一梦想,argument这个属性是一个数组,他的值就是我们的函数传进来的参数数组,我们可以用它来模拟实现函数重载。我们可以这样写
function MyObj()
{
this.cMsg = ""
this.Msg=function()
{
if(argument.length==1)
{
this.cMsg = argument[0]
}
return this.cMsg
}
}
var Obj = new MyObj()
Obj.Msg("leowoo")
window.alert(Obj.Msg())
看到这里我们也许需要在给属性赋值的时候触发一个事件来运行一个外部函数怎么办?
我们前面讨论到,JavaScript的变量可以是任意类型的对象,他当然也可以是一个函数引用,所以我们只需要在对象里给出一个空属性让其他程序来给他赋值一个方法就可以了,然后我们再需要运行的地方调用它,虽然他可能不存在。
代码如下:
function MyObj()
{
this.cMsg = ""
this.onMsg = null
this.Msg=function()
{
if(argument.length==1)
{
if(typeof this.onMsg == "function")
{
this.onMsg()
}
this.cMsg = argument[0]
}
return this.cMsg
}
}
var Obj = new MyObj()
Obj.onMsg = function()
{
window.alert("onMsg")
}
Obj.Msg("leowoo")
window.alert(Obj.Msg())
当然我们也可以传值给这个事件
部分代码如下:
if(typeof this.onMsg == "function")
{
this.onMsg(this.Msg)
}
…………
Obj.onMsg = function(PMsg)
{
window.alert("onMsg "+PMsg)
}
好了,现在我们用JavaScript模拟类的实现基本就算结束了,如果还有什么需要补充的或疑问,请联系我QQ:27860381
有时间我在整理一下JavaScript模拟模板技术是怎样实现的。
LeoWoo(六月初六)
写于2006年8月3号 下午4点
修改于2006年8月9号 下午6点