在很久很久以前,我发布了一篇博文探讨了如何用JavaScript实现简单的继承,详见 [ JavaScript面向对象编程(1) :继承 ] .这种方式对我来说直观而容易理解,有效防止侧漏又不伤手,让我整夜安睡,所以我一直在用它,嘿,还真对得起咱这张脸.
但它有一个很大的缺点: 所有的方法变量都是暴露在外的,都能随意访问,这一点都不符合面相对象编程的"封装"的要求嘛!作为追求完美的我,可以容忍goto语句,可以容忍i,j,k,l,mm,hhh这样的变量名称,可以容忍一个函数包含3000多行代码,可以容忍<爱情买卖>,可以容忍铺天盖地的"给力",但是!我!怎么能!容忍!OOP的三大基本特征遭到亵渎?! 不知道OOP的三大基本特征? 不知道? 真不知道? ..........那你来这儿干什么?
所以后来我又构思了一种方案,可以解决上面这个问题.
这个新方案使用了闭包技术(你又不知道闭包?.....吐血中...), 有以下特性:
1 跟前一个方案一样,在在"构造函数"(嗯,就是你自己为了定义一个对象而写的那个函数啦---怎么这么拗口)第一行必须调用工具函数Class()来添加对继承,重载以及封装的支持.当然这个函数的代码与第一个方案是不同的,等会儿我会给出这个函数的代码
2 用public.函数名=function(...){...}的形式来定义"公有函数",公有函数就是...,不说了,你懂得.3 用局部变量方式定义"私有成员",这里加上引号是指并不是真正意义上的私有成员,只是与私有成员类似,外部使用者不可见.
4 公有函数可以直接用名称调用"私有成员",但必须用this.函数名来调用其它公有函数.
5 "私有函数"可以直接用名称调用"私有成员",但必须用public.函数名来调用公有函数.
6 父对象的公有函数可用父对象名_函数名的方式使用
7 最后必须用return NewInstance();来得到真正的对象的实例(我叫它接口,:p),等会儿我会给出函数NewInstance的代码.
8 生成实例时可以用new也可以不用.如
var myClass = new MyClass();
和
var myClass = MyClass();
都有效
可以看出4和5是这个方案的硬伤,曾经让我几乎要无情的抛弃这个方案.不过我已经对解决它们有了一些头绪,在不久的将来(就三五年吧)我会解决它们.
下面是最关键的函数Class和NewInstance的代码,从这两个函数可以看出,实际上我是在"构造函数"中创建了一个名叫public的局部对象,并把所有希望能够在外部访问的函数(包括父对象的)都添加到public,然后将public作为真正的对象的实例返回到外部,根据闭包原理"构造函数"中的局部变量和函数和public在同一个作用域,所以public的函数可以访问"构造函数"中的局部变量和函数,但其他外部函数和代码则不能.
// //函数Class,用来添加继承和封装的支持 // //function Class([Base[,param1[,param2[,...]]]]) // //参数 : Base : 父对象的名称 // Param1... : 父对象的参数 // function Class(_base) { public=function(){}; //创建一个名叫public的局部变量 if(typeof(_base)=="function" ) {//如果指定了父对象 var baseName=FunctionName(_base); var __super=_base; //带参数初始化基类 var callString=""; for(i=1;i<arguments.length;i++){ if(i>1) callString+=","; callString+="arguments["+i+"]"; } //用父对象的public变量作为自己的public变量,这样就达到了继承的目的 public=eval("__super("+ callString +");"); var publicKeys=new Array(); for(key in public){ publicKeys.push(key); } //保存父对象的方法使之可用父对象名_方法的方式使用 for(var i=0;i<publicKeys.length;i++) { eval("public."+ baseName + "_" + publicKeys[i] + "=public[/"" + publicKeys[i] + "/"]"); } __super=null; publicKeys=null; } }
// //NewInstance 简单的返回public // function NewInstance() { return public; }
下面是使用方法的示例代码
function Person(theSex, theName) { Class(); //私有成员 var myName="No Name"; var mySex ="Unknown"; //公有函数 public.getName=function() {return myName; } public.setName=function(newName) { myName = newName; } public.getSex=function() { return mySex; } public.setSex=function(newSex) { mySex = newSex; } public.shout=function(str) { window.alert("A person(name=" + this.getName() + ",sex="+this.getSex() + ") is shouting : " + str); } //初始化 if(arguments.length>0) mySex= theSex; if(arguments.length>1) myName = theName; return NewInstance(); } function Boy(theName) { Class(Person,"M"); //公有函数 //覆盖父类函数 public.shout=function(str) { window.alert("A boy(name=" + this.getName() + ") is shouting : " + str); } public.shout2=function(str){ this.shout(str); //调用父类函数 this.Person_shout(str); } //初始化 if(arguments.length>0) public.setName(theName); return NewInstance(); } var aPerson=Person("F","Ben"); var aBoy=Boy("TOM"); aPerson.shout("Person.shout"); aBoy.shout("Boy.shout"); aBoy.Person_shout("Person.shout"); aBoy.shout2("Boy.shout2"); }