随着 AJAX 和 RIA 技术的发展,JavaScript 被广泛的使用,并在开发中发挥着越来越重要的作用。JavaScript 提供了特有的类机制,但是在语法习惯上与传统面向对象的语言有很大的不同,这使得不少的 JavaScript 开发人员感到比较迷惑,而 dojo 作为功能强大的 JavaScript 类库,有功能完整的类机制实现。本文将通过实例介绍 dojo 的类机制,这是 dojo 提供的一种强大和灵活的功能,其 dijit UI 组件框架也是以此为基础实现的。
1. 使用 dojo 定义类
声明 dojo 类是通过 dojo.declare() 方法来实现的,如我们想要定义一个名为 com.levinzhang.Person 的类,该类有 name、age 属性和 getName、getAge 方法:
dojo.declare("com.levinzhang.Person", null,{ name:null, age:null, constructor: function(name,age){ this.name = name; this.age = age; }, getName:function(){ return this.name; }, getAge:function(){ return this.age; } });
除了前面提到的属性的和方法以外,在代码中我们还定义了一个名为 constructor 的方法,这个方法在 dojo 的类机制中至关重要,当我们实例化该类得到对象的时候,该方法将会被调用,从而完成初始化的操作。
dojo 的 declare 接受三个参数,分别为类的名称、要继承的父类以及该类的属性和方法。实例化类的语法也很简洁,与实例化普通的 JavaScript 类并无分别:
var person = new com.levinzhang.Person("levinzhang",30); alert(person.getName());// 将会提示出 levinzhang
2. 实现静态变量
在常见的面向对象语言中,经常会使用到类层次的静态变量,而通过 dojo 定义的类也能实现静态变量的需求,不过静态变量仅限于数组和对象类型。
staticInfo:{count:0}, constructor: function(name,age){ this.name = name; this.age = age; ++this.staticInfo.count; }
如上所示,如果定义了数组和对象,而没有在构造方法中进行修改的话,这个对象将会成为该类的静态属性,测试代码如下:
var person = new com.levinzhang.Person("levinzhang",30); alert(person.staticInfo.count);// 此时将会提示出 1 var person2 = new com.levinzhang.Person("levin",30); alert(person2.staticInfo.count);// 此时将会提示出 2
需要注意的两点是:1)对于原始类型的变量如数字、布尔值和字符串,dojo 的类机制并没有提供实现静态属性的功能;2)如果定义的数组或对象属性在 constructor 方法中被重新赋值,那么该属性将不再是静态属性,而是每个实例化对象都持有一份属于自己的备份了。
3. 使用 dojo 实现继承
在 JavaScript 中没有直接实现继承的关键字,因此关于继承有多种的实现方式,代表性的是类式继承和原型式继承,但是不管哪种继承方式都需要开发人员对JavaScript 语言本身有着很深厚的了解。dojo 对JavaScript 的继承机制进行了很好的封装,可以实现功能强大的类定义,我们将对一些常见的功能进行介绍。
dojo.declare 方法中的第二个参数,是指明要继承的父类的,该参数可以为 null(要定义的类没有父类)、单个类(要定义的类继承自一个父类)或数组(要定义的类继承自多个父类)。
1) 单继承
我们要定义一个名为 com.levinzhang.Employee 的类,继承自 com.levinzhang.Person,并要添加名为 workExperience 的属性、重写 getName 方法等功能:
dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{ workExperience:null, constructor: function(name,age,workExperience){ this.workExperience = workExperience; }, getWorkExperience:function(){ return this.workExperience; }, getName:function(){ return "Employee:"+this.name; }, getInput:function(){ return 5000; } });
在以上的代码中,我们定义的 com.levinzhang.Employee 继承了 com.levinzhang.Person 并添加了自定义的方法。测试代码如下:
var employee = new com.levinzhang.Employee("levin",30,4); alert(employee.getName());// 将提示出 Employee:levin alert(employee.getWorkExperience());// 将提示出 4 alert(employee.getAge());// 将提示出 30
可以看到在 Employee 的实例中,我们能够调用父类中定义的方法。而在类的 constructor 初始化方法中,我们并没有调用父类相关的方法,但是我们成功初始化了 name 和 age 两个属性,这是因为 dojo 会自动调用父类的初始化方法,完成继承要求的相关初始化工作。
2) 多继承
dojo 支持多继承的功能, dojo 实现了被 Python 和很多支持多继承语言使用的 C3 算法。使用 dojo 的多继承功能时,需要注意的是:只有数组中的第一个元素会作为真正的父类,而其它的元素则是通过 mixin 的方式进行属性添加以构建原型链的。
如我们需要定义一个类来表示公司中的股票持有者(com.levinzhang.Shareholder),而公司中的员工可能也会持有股票,于是我们定义一个名为 com.levinzhang.ShareholderEmployee 的类继承自 com.levinzhang.Shareholder 和 com.levinzhang.Employee。
dojo.declare("com.levinzhang.Shareholder", com.levinzhang.Person,{ share:null, constructor: function(args){ this.share = args.share; }, getShare:function(){ return this.share; } }); dojo.declare("com.levinzhang.ShareholderEmployee", [com.levinzhang.Employee,com.levinzhang.Shareholder],{ getInfo:function(){ alert("I'm an Employee with stock.My share is "+this.getShare()+"."+"My name is "+this.getName()+"."); } });
在以上的代码中,我们调整了原有的初始化传入参数的格式,由传入多个参数改为传入一个简单 JavaScript 字面量对象的方式(原有的代码也要稍作调整),并通过多继承的方式实现了一个类用来描述持有股票的员工。测试代码如下:
var shareholderEmployee = new com.levinzhang.ShareholderEmployee({name:"levin",age:30,workExperience:4,share:300}); shareholderEmployee.getInfo(); // 将会提示出“I'm an Employee with stock.My share is 300. My name is Employee:levin.”
关于 dojo 多继承的更多话题,请参考 dojo 的文档资料。
3) 调用父类的方法
在编程中,我们经常会遇到在子类的某个方法中需要调用父类的方法来协作完成功能。如我们定义名为 com.levinzhang.Manager 的类,该类继承自 com.levinzhang.Employee 类,并重写 getInput 方法,Manager 的收入分为两部分,一部分是与 com.levinzhang.Employee 相同的固定收入,另一部分是与管理经验相关的其它收入,这样在定义 Manager 的时候,就需要调用父类的方法,实现方式如下:
dojo.declare("com.levinzhang.Manager", com.levinzhang.Employee,{ manageExperience:null, constructor: function(args){ this.manageExperience = args.manageExperience; }, getInput:function(){ var fromBase = this.inherited(arguments); return fromBase+1000*this.manageExperience; } });
从以上代码可以看到,通过 inherited 方法的使用,使得 Manager 可以调用父类的方法。测试代码如下:
var manager = new com.levinzhang.Manager({name:"levin",age:30,workExperience:4,manageExperience:2}); alert(manager.getInput());//7000
在以上的测试代码中,getInput 的返回值为 7000,说明该值为子类和父类方法共同确定的。
除了使用 inherited 来调用父类中的方法以外,从 dojo 的 1.4 版本开始提供了链式调用父类方法的功能,能够通过设置自动调用父类的方法,并且支持类似于 AOP 的 before 或 after 配置(dojo 正在开发中的 1.7 版本,提供了更为丰富的 AOP 功能,我们将会持续关注)。
4. Dojo 类机制的其它功能
除了以上介绍的类定义相关功能以外,dojo 还提供了许多的便利的工具类供使用。
dojo 类所生成对象具有一些特有的属性和方法,常见的如 isInstanceOf 方法和 declaredClass 属性,isInstanceOf 方法判定对象是否为某个类的实例,而 declaredClass 属性则能够表明该对象的声明类是什么。如:
var manager = new com.levinzhang.Manager({name:"levin",age:30,workExperience:4,manageExperience:2}); alert(manager.isInstanceOf(com.levinzhang.Employee));// 提示为 true alert(manager.isInstanceOf(com.levinzhang.Person));// 提示为 true alert(manager.declaredClass);// 提示为“com.levinzhang.Manager”
类机制还涉及到的包管理等功能,限于篇幅,不再展开叙述,感兴趣的读者可以参考 dojo 的在线文档或图书。
5. 小结
JavaScript 本身的类机制比较复杂,对开发人员有着较高的要求,而 dojo 提供了功能强大的类功能,有些降低了开发的难度。本文简单介绍了 dojo 类机制的基本功能,包括类定义、继承、静态属性等,这是 dojo 最基础也是最核心的内容之一,dojo 的许多高级功能都是基于此来实现的,因此了解这部分功能的使用方式甚至源码实现对于整体把握 dojo 框架都大有裨益。
参考资料:
- 《JavaScript 权威指南》 David Flanagan 著 张铭泽译
- http://docs.dojocampus.org/
- http://blog.csdn.net/dojotoolkit/
- http://dojotoolkit.org/
关于作者:
张卫滨,关注企业级 Java 开发和 RIA 技术,个人博客: http://lengyun3566.iteye.com
感谢张凯峰对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
活动推荐:
2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。
评论