使用dojo/_base/declare创建优雅的javascript类
dojo/_base/declare模块是dojo创建类的基础,declare允许多继承,这使得开发人员能够灵活创建代码,避免编写相同的代码。Dojo, Dijit, 和 Dojox 模块全部使用了declare。在下面的教程中,你将学习为什么你需要这么做。
开始
开始之前请确保你已经对AMD模块的概念有所了解。
使用dojo创建基本的dojo类
declare函数是在dojo/_base/declare模块中定义的。declare函数接收三个参数:className, superClass,和properties即类名、超类和属性。
ClassName(类名)
ClassName参数表示将要创建的类的名称包括命名空间。命名的类将放到全局作用域。ClassName也可以表示命名空间中的继承链。
命名类
// Create a new class named "mynamespace.MyClass"
declare("mynamespace.MyClass", null, {
// Custom properties and methods here
});
这样一个名为mynamespace.MyClass的类就可以在全局应用程序中使用。
"匿名"类
// Create a scoped, anonymous class
var MyClass = declare(null, {
// Custom properties and methods here
});
现在Myclass只能在它给定的作用域中使用。
SuperClass(es)(超类)
这个SuperClass参数可以为空、可以是一个已有的类或者是多个已有类的一个数组。如果一个新的类从多个类继承,那么多个类列表中的第一个类将作为基本原型,剩下的将被认为是“混入”。
没有继承的类
var MyClass = declare(null, {
// Custom properties and methods here
});
null表示该类没有从任何类继承。
单继承的类
var MySubClass = declare(MyClass, {
// MySubClass now has all of MyClass's properties and methods
// These properties and methods override parent's
});
新的MySubclass类将继承MyClass类的属性和方法,父类的属性和方法可以通过第三个参数增加新的键来重写(override)。
多继承的类
var MyMultiSubClass = declare([
MySubClass,
MyOtherClass,
MyMixinClass
],{
// MyMultiSubClass now has all of the properties and methods from:
// MySubClass, MyOtherClass, and MyMixinClass
});
declare第二个参数使用类的数组来表示多继承(这里是匿名类,所以这里的declare的第一个参数代表开头说的第二个参数!)。属性和方法的继承顺序为自左向右,数组的第一个类作为基本原型,其余的类将混入到该类。
** 如果属性或方法在一个以上的继承的类被指定,将使用从最后一个继承的类的属性或方法。**
属性和方法对象
declare的最后一个参数是一个包含原型的方法和属性的对象。通过此参数中的属性和方法类重写继承类中的同名属性或方法。
自定义的属性和方法
// Class with custom properties and methods
var MyClass = declare(MyParentClass, {
// Any property
myProperty1: 12,
// Another
myOtherProperty: "Hello",
// A method
myMethod: function(){
// Perform any functionality here
return result;
}
});
示例:基本类的创建和继承
下面的代码创建了一个小工具继承自dijit/form/Button:
define([
"dojo/_base/declare",
"dijit/form/Button"
], function(declare, Button){
return declare("mynamespace.Button", Button, {
label: "My Button",
onClick: function(evt){
console.log("I was clicked!");
this.inherited(arguments);
}
});
});
从上面的代码片段,很容易得到下面的结论:
- 这个类的名字是mynamespace.Button
- 这个类可以通过全局变量mynamespace.Button或者模块的返回值来引用
- 这个类从dijit/form/Button继承(按钮的依赖模块)
- 这个类设置了一些自定义的属性和方法
让我们通过学习constructor(构造方法)来深入挖掘使用dojo创建类。
constructor Method(构造方法)
构造方法(constructor)是类的一个特别的方法。构造方法在实例化类的时候被触发,并且在新的对象的作用域内执行。这意味着this关键字引用的类的实例而不是原来的类。构造方法同样接收任意数量的特定实例的参数。
// Create a new class
var Twitter = declare(null, {
// The default username
username: "defaultUser",
// The constructor
constructor: function(args){
declare.safeMixin(this,args);
}
});
下面来创建实例:
var myInstance = new Twitter();
本实例中所使用的用户名将会是“ defaultUser”,因为为该实例提供具体的用户名。要使用safeMixin方法,提供了一个用户名参数:
var myInstance = new Twitter({
username: "sitepen"
});
现在这个实例使用sitepen作为用户名设置。
** declare.safeMixin在类的创建和继承中也同样有用。按照API文档描述: **
"这个方法用来像lang._mixin那样混合属性,但是它跳过了一个构造属性,同时像dojo/_base/declare那样装饰功能。它目的是让dojo/_base/declare产生的类或者对象使用。使用declare.safeMixin混合的功能可以像正常方法一样使用this.inherited(),这个功能用来实现通过declare()产生的构造函数的extend()方法"
declare.safeMixin是用来创建多个可选项类的最佳拍档。
继承
如上所述,继承在declare的第二个参数中定义。类自左向右混合每级类的属性和方法,后面的方法和属性较之前已经定义的具有较高的优先级,也就意味着后面的类会覆盖前面类定义的属性或方法。看下面的例子:
// Define class A
var A = declare(null, {
// A few properties...
propertyA: "Yes",
propertyB: 2
});
// Define class B
var B = declare(A, {
// A few properties...
propertyA: "Maybe",
propertyB: 1,
propertyC: true
});
// Define class C
var C = declare([mynamespace.A, mynamespace.B], {
// A few properties...
propertyA: "No",
propertyB: 99,
propertyD: false
});
继承类的属性结果如下:
// Create an instance
var instance = new C();
// instance.propertyA = "No" // overridden by B, then by C
// instance.propertyB = 99 // overridden by B, then by C
// instance.propertyC = true // kept from B
// instance.propertyD = false // created by C
对原型继承有个清醒的认识是十分重要的。当一个属性从一个对象实例入口读取时,会先检查这个对象实例是否定义了这个属性。如果没有这个属性则从原型链遍历并且返回原型链上第一个含有该属性的对象上该属性的值。当一个值指配到一个属性时永远是在该对象的实例上,不会在原型上。这样做的结果是,共享相同原型的所有对象将返回原型上定义的相同的属性值,除非该实例上已经设置了该值。这可以很容易地在你的类声明中定义基本数据类型(数字,字符串,布尔值)的默认值,并根据你的需要对实例对象进行更新。但是,如果分配对象原型上的属性值(对象,数组),每一个实例都将操纵相同的共享值。考虑以下几点:
var MyClass = declare(null, {
primitiveVal: 5,
objectVal: [1, 2, 3]
});
var obj1 = new MyClass();
var obj2 = new MyClass();
// both return the same value from the prototype
obj1.primitiveVal === 5; // true
obj2.primitiveVal === 5; // true
// obj2 gets its own property (prototype remains unchanged)
obj2.primitiveVal = 10;
// obj1 still gets its value from the prototype
obj1.primitiveVal === 5; // true
obj2.primitiveVal === 10; // true
// both point to the array on the prototype,
// neither instance has its own array at this point
obj1.objectVal === obj2.objectVal; // true
// obj2 manipulates the prototype's array
obj2.objectVal.push(4);
// obj2's manipulation is reflected in obj1 since the array
// is shared by all instances from the prototype
obj1.objectVal.length === 4; // true
obj1.objectVal[3] === 4; // true
// only assignment of the property itself (not manipulation of object
// properties) creates an instance-specific property
obj2.objectVal = [];
obj1.objectVal === obj2.objectVal; // false
为了避免无意共享所有实例中的数组和对象,对象属性必须声明为空值并且在构造函数中进行初始化:
declare(null, {
// not strictly necessary, but good practice
// for readability to declare all properties
memberList: null,
roomMap: null,
constructor: function () {
// initializing these properties with values in the constructor
// ensures that they ready for use by other methods
// (and are not null or undefined)
this.memberList = [];
this.roomMap = {};
}
});
请参阅dojo/_base/declare文档获取更多信息。
this.inherited
虽然完全覆盖重写方法是可行的,但是有时继承链上每个类的构造函数应该保持他们原有的功能来执行。这时候this.inherited(arguments)语句就派上用场了。这个this.inherited(arguments)调用父类的同名方法,考虑一下几点:
// Define class A
var A = declare(null, {
myMethod: function(){
console.log("Hello!");
}
});
// Define class B
var B = declare(A, {
myMethod: function(){
// Call A's myMethod
this.inherited(arguments); // arguments provided to A's myMethod
console.log("World!");
}
});
// Create an instance of B
var myB = new B();
myB.myMethod();
// Would output:
// Hello!
// World!
** this.inherited方法可以被子类的代码在任何时候调用,在有些情况下,你想在子类的中间或者最后调用inherited()方法,也就是说,你不能在构造函数中调用它。 **
最后
声明函数declare是dojo工具包用来创建模块化、可重用类的关键。declare允许使用多重继承和任意数量的属性和方法来创建复杂的类的。更好的是declare简单易学,避免开发人员编写重复的代码。