《悟透JavaScript》之 甘露模型
作者:李战 来源:软件真谛 发布时间:2008-06-09 14:03:00
注意:如果您尚未阅读过原来那篇老文章《悟透JavaScript》,请先行阅读该文,以了解上下文关系。
在上面的示例中,我们定义了两个语法甘露,一个是Class()函数,一个是New()函数。使用Class()甘露,我们已经可以用非常优雅的格式定义一个类。例如前例中的:
var Employee = Class(Person, //派生至Person类
{
Create: function(name, age, salary)
{
Person.Create.call(this, name, age); //调用基类的构造函数
this.salary = salary;
},
ShowMeTheMoney: function()
{
alert(this.name + " $" + this.salary);
}
});
这种类的写法已经和C#或Java的格式非常相似了。不过,其中调用基类的构造函数还需要用“Person.Create.call(this, name, age)”这样的方式来表达。这需要用到基类的类名,并要用call这种特殊的方式来传递this指针。这和C#的base()以及Java的super()那样的简介调用方式比起来,还需要进一步美化。
而New()函数的使用也不是很爽。前例中需要用“New(Employee, ["Steve Jobs", 53, 1234])”这样的方式来创建对象,其中第一个参数是类,其他构造参数需要用数组包起来。这和JavaScript本来那种自然的“new Employee("Steve Jobs", 53, 1234)”比起来,丑陋多了。这也需要美化。
为了实现这些美化工作,我们需要回顾一下new一个对象的实质。前面我们说过:
var anObj = new aClass();
相当于先创建一个空白对象anObj,然后将其作为this指针调用aClass()函数。其实,这个过程中还有一个关键步骤就是将aClass的prototype属性,赋值给anObj内置的prototype属性。尽管我们无法访问到anObj内置的prototype属性,但它却为对象提供了可以调用的方法。
由于前例中的Class()语法甘露实际上是构造了一个原型,并将这个原型挂在了相应的原型链上。由于它返回的是一个对象而不是函数,因此由它定义出来的Person和Employee类也都只是对象而不是函数,无法用new Person()或new Employee()这样的方式来创建对象。要创基于一个原型来创建对象,就需要借助New()语法甘露来中转这个原型。
那么,如果我们让Class()语法甘露返回一个函数而不是对象,不就可以用new Person()和new Employee()这种方式来创建对象了吗?而且,我们可为这个返回函数创建一个继承至相关原型链的原型对象,并设置到该函数的prototype属性。这样,我们用new方式创建这个类函数的对象时,就自然地继承了该类的原型了。
那么,我们让Class()语法甘露返回什么函数呢?因为Class()语法甘露返回的函数是用来创建对象的,当然应该返回该类的构造函数了,正好可以是类定义参数中的Create方法啊。这样一来,我们也无需在New()语法甘露中间接调用Create构造函数了,事实上New()语法甘露可以完全扔掉了。
于是,我们就有了下面这个精简甘露模型的例子:
http://www.leadzen.cn/Books/WuTouJavaScript/1/JS24.htm
<script type="text/javascript">
//定义类的语法甘露:Class()
//最后一个参数是JSON表示的类定义
//如果参数数量大于1个,则第一个参数是基类
//第一个和最后一个之间参数,将来可表示类实现的接口
//返回值是类,类是一个构造函数
function Class()
{
var aDefine = arguments[arguments.length-1]; //最后一个参数是类定义
if(!aDefine) return;
var aBase = arguments.length>1 ? arguments[0] : object; //解析基类
function prototype_(){}; //构造prototype的临时函数,用于挂接原型链
prototype_.prototype = aBase.prototype; //准备传递prototype
var aPrototype = new prototype_(); //建立类要用的prototype
for(var member in aDefine) //复制类定义到当前类的prototype
if(member!="Create") //构造函数不用复制
aPrototype[member] = aDefine[member];
if(aDefine.Create) //若有构造函数
var aType = aDefine.Create //类型即为该构造函数
else //否则为默认构造函数
aType = function()
{
this.base.apply(this, arguments);
};
aType.prototype = aPrototype; //设置类(构造函数)的prototype
aType.Base = aBase; //设置类型关系
aType.prototype.Type = aType; //为本类对象扩展一个Type属性
return aType; //返回构造函数作为类
};
//根类object定义:
function object(){} //定义小写的object根类,用于实现最基础的方法等
object.prototype.isA = function(aType) //判断对象是否属于某类型
{
var self = this.Type;
while(self)
{
if(self == aType) return true;
self = self.Base;
};
return false;
};
object.prototype.base = function() //调用基类构造函数
{
var Caller = object.prototype.base.caller;
Caller && Caller.Base && Caller.Base.apply(this, arguments);
};
//语法甘露的应用效果:
var Person = Class //默认派生自object基本类
({
Create: function(name, age)
{
this.base();
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " + this.name + ", " + this.age + " years old.");
}
});
var Employee = Class(Person, //派生自Person类
{
Create: function(name, age, salary)
{
this.base(name, age); //调用基类的构造函数
this.salary = salary;
},
ShowMeTheMoney: function()
{
alert(this.name + " $" + this.salary);
}
});
var BillGates = new Person("Bill Gates", 53);
var SteveJobs = new Employee("Steve Jobs", 53, 1234);
BillGates.SayHello();
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
var LittleBill = new BillGates.Type("Little Bill", 6); //用BillGate的类型建LittleBill
LittleBill.SayHello();
alert(BillGates.isA(Person)); //true
alert(BillGates.isA(Employee)); //false
alert(SteveJobs.isA(Person)); //true
</script>
这个精简甘露模型模拟出来的类更加自然和谐,而且比前面的甘露模型更加精简。其中的Class()函数虽然很简短,但却是整个模型的关键:
Class()函数将最后一个参数当作类定义,如果有两个参数,则第一个参数就表示继承的基类。如果多于两个参数,则第一个和最后一个之间的参数都可以用作类需要实现的接口声明,保留给将来扩展甘露模型使用吧。
使用Class()函数来定义一个类,实际上就是为创建对象准备了一个构造函数,而该构造函数的prototype已经初始化为方法表,并可继承上层类的方法表。这样,当用new操作符创建一个该类对象时,也就很自然地将此构造函数的原型链传递给了新构造的对象。于是,就可以采用象“new Person("Bill Gates", 53)”这样的语法来创建对象了。
猜你喜欢
- -- 任意的测试表 代码如下:CREATE TABLE test_delete( name varchar(10), value INT )
- select for update 这个是行级锁 当 commit或者rollback时,锁释放 记得打开事务,比如jdbc里面 setAu
- 如果有空格就用%20代替,如果有其它字符就用%ASCII代替,如果有汉字等四个字节的字符,就用两个%ASCII来代替。不过有时候我们也需要将
- Asp开发 联通CDMA以下是在开发wap中的随笔,其中一些对于“老鸟”来说,谈不上什么,希望对初学者有所帮助,大家有什么小技巧,欢迎顶上来
- 阅读上一章:[翻译]标记语言和样式手册 Chapter 15 为body指定样式Chapter 16 下一步现在你知道了如何使用标准改进你的
- 用字符串就可以轻松地获取每一个文件的名称和扩展名,但不要乱用:<%Function getFilename(text)tex
- 毫无疑问,Google是当今世界上最成功的互联网公司之一,但是Google也曾推出过一些失败的实验品。还记得Google Accelerat
- 在当今用户的显示器越来越大的今天,之前的1024*768固宽布局有点越来越不合时宜,对大屏幕的用户而言,两侧空空的留白给人第一眼的印象是严重
- 用QQ聊过天的朋友都对它的自动隐藏窗口功能爱不释手,它可以使窗口显得清爽整洁而且富有动感,笔者的几个朋友都想在自己的网页中加入类似的东东,经
- 昨天有位同事说,他的网页查询过程中发现普通索引和唯一索引的效率是有差别的,普通索引比唯一索引快,今天在我的虚拟机中布置了环境,测试抓图如下:
- 论证完使用target=_blank并非绝对错误之后,分场景探讨如何减少新开窗口。自有意识注意这个问题,是看到蓝色经典Plod大叔在04年提
- rs.open sql,conn,A,B A: ADOPenforwardonly (=0) 只读,且当前数据记录只能向下移动。 ADOPe
- 这个问题困扰了我很长很长的时间,在跨域获取数据的时候就要用到服务器端的对象,以前一直用的是Msxml.XMLHTTP。但是问题太多了,特别严
- 我们知道,数组的sort方法可以对数组元素进行排序,默认是按ASCII字母表顺序排序。如果要根据其他的顺序排序就需要为sort方法提供一个比
- #BEGIN CONFIG INFO#DESCR: 4GB RAM, 只使用InnoDB, ACID, 少量的连接, 队列负载大#TYPE:
- 最近自己很关注文档的撰写,包括如何制作PPT。因为发现自己在表达想法和观点的时候,从自己的语言到文字都异常的欠缺。常常需要“高人”帮忙翻译。
- (1)应用于客户需要与不同的数据源进行交互时。数据可能来自不同的数据库,他们都有各自不同的复杂格式。但客户与这些数据库间只通过一种标准语言进
- 最近一直在做Dnn模块的开发,过程中碰到这么一个问题,需要同时插入N条数据,不想在程序里控制,但是SQL Sever又不支持数组参数.所以只
- 我们可以利用err对象来判断。当程序没有出现错误就说明已经执行了sql操作: sql="insert into
- XML(可扩展标记语言)已成为Web应用中数据表示和数据交换的标准,随着Internet的快速发展,尤其是电子商务,Web服务等应用的广泛使