原文:Classy JavaScript with dojo/_base/declare
dojo/_base/declare模块是在Dojo Toolkit中创建类的基石。declare允许多重继承,允许开发者创建灵活的代码避免重复造轮子。Dojo,Dijit和Dojox中的模块都使用了declare;在本教程中,你将学到为什么你也应该使用它。
开始
首先要确保你复习了在讲解模块的教程中提出的概念。
在Dojo中基于Dojo的类来创建类
declare函数定义在dojo/_base/declare模块中,declare有三个参数:className(类名),superClass(超类/父类)和properties(属性)。
类名
className参数标识要创建的类的名称,包括命名空间。命名的类呈现在全局作用域内。className也可以通过命名空间标识继承链。
1 // create a new class named "mynamespace.MyClass" 2 declare("mynamespace.MyClass", { 3 4 // Custom properties and methods here 5 6 });
一个名称为mynamespace.MyClass的类在应用的全局作用域内诞生了。
如果以后要给Dojo的parser使用,才应创建命名类。其他情况下,都应该省略className参数。
1 var MyClass = declare(null, { 2 3 });
MyClass只在给定的作用域内容有效。
超类
超类参数可以为null,也可以为一个存在的类或类的数组。如果一个新的类继承了多个类,以列表中的首个类为原型,其余的混入其中。
没有任何继承的类
1 var MyClass = declare(null, { 2 3 });
null标识该类没有继承自任何类。
继承另一个类
var MySubClass = declare(MyClass, { });
MySubClass继承MyClass的属性与方法。父类中的属性或方法将会被第三个参数中定义的键所覆盖,很快就会讲到第三个参数。
继承自多个类
1 var MyMultiSubClass = declare([ 2 MySubClass, 3 MyOtherClass, 4 MyMixinClass 5 ], { 6 7 8 9 });
一个类的数组多继承。属性与方法从左至右继承过来。第一个来作为基础原型,剩下的混合如其中。
如果不同的类中有同名的属性或方法,靠后面的类中的属性与方法将被使用。
属性与方法对象(declare的第三个参数)
declare的最后一个参数是一个对象包含了类的属性与方法。参数的中的属性与方法将会覆盖超类中的属性与方法。
自定义属性与方法
1 // Class with custom properties and methods 2 var MyClass = declare(MyParentClass, { 3 // Any property 4 myProperty1: 12, 5 // Another 6 myOtherProperty: ‘Hello‘, 7 // A method 8 myMethod: function() { 9 10 // Perform any functionality here 11 12 return result; 13 } 14 });
例子:基础的类构建与继承
下面的代码通过继承dijit/form/Button创建一个挂件。
1 define([ 2 ‘dojo/_base/declare‘, 3 ‘dijit/form/Button‘ 4 ], function(declare, Button) { 5 return declare(‘mynamespace.Button‘, Button, { 6 label: ‘My Button‘, 7 onClick: function(evt) { 8 console.log(‘I was clicked‘); 9 this.inherited(arguments); 10 } 11 }); 12 });
从上面这段代码可以看出:
- 该类类名为mynamespace.Button
- 该类可以作为mynamespace.Button全局引用或通过模块返回
- 该类继承自dijit/form/Button
- 该类集合了一些属性与方法
让我们通过学习关于constructor方法的知识类深入理解使用Dojo构建类。
构造方法
类有个特殊的方法是constructor。这个方法在类实例化的时候被调用,在这个新对象的作用域内执行。这意味着constructor方法内容的this代表这个实例,不是原来的类。constructor方法也可以接受一些实例化时需要的参数。
1 // Create a new class 2 var Twitter = declare(null, { 3 // the default username 4 username: ‘defaultUser‘, 5 6 // the constructor 7 constructor: function(args) { 8 declare.safeMixin(this, args); 9 } 10 });
接下来创建一个实例:
1 var myInstance = new Twitter();
username使用默认的defaultUser,除非为实例提供了指定的设置。为了利用safeMixin方法,传递一个username参数:
1 var myInstance = new Twitter({ 2 username: ‘sitepen‘ 3 });
这样myInstance的username就被设置为sitepen了。
declare的safeMixin方法构建与继承类时是很有用的。API文档是这样描述的:
这个方法用于混合属性像lang._mixin做的那样,但是会跳过构造方法与装饰方法就想dojo/_base/declare做的那样。它用于类与对象在dojo/_base/declare中。使用declare.safeMixin混合的方法可以使用this.inherited。这个函数用于实现使用declare产生的构造器的extend方法。
当使用很多选项构建类时declare.sageMixin是很有用的。
继承
如上述,继承是使用declare的第二个参数定义的。超类们从左至右混合类的属性与方法,后面的会覆盖已定义的属性与方法。如下:
1 // Define class A 2 var A = declare(null, { 3 // A few properties ... 4 propertyA: "yes", 5 propertyB: 2 6 }); 7 8 // Define class B 9 var B = declare(null, { 10 // A few properties ... 11 propertyA: "Maybe", 12 propertyB: 1, 13 propertyC: true 14 }); 15 16 // Define class C 17 var C = declare([mynamespace.A, mynamespace.B], { 18 // A few properties... 19 propertyA: "No", 20 propertyB: 99, 21 propertyD: false 22 });
对于继承类属性的结果如下:
1 // Create an instance 2 var instance = new C(); 3 4 // instance.propertyA = "No" // overridden by B, the by C 5 // instance.propertyB = 99 // overridden by B, the by C 6 // instance.propertyC = true // kept from B 7 // instance.propertyD = false // create by C
清晰的理解原型继承非常重要。当一个属性从一个实例对象中读取时,实例首先检查自己是否定义该属性。如果没有,则顺原型链查找知道找到最近的属性值返回。当给一个属性赋值时,都是给实例本身的属性赋值,从不会影响原型。这样做的结果就是所有对象都共享一个原型将会返回定义在原型上的属性值,除非实例自己就有这个属性。这就让在类中定义原始数据类型的默认值,根据需要在实例上更新他们变得容易。然而如果你指派的属性值是一个对象或数组,每个实例将维护共享同一个值。考虑一下下面的代码:
1 var MyClass = declare(null, { 2 primitiveVal: 5, 3 objectVal: [1, 2, 3] 4 }); 5 6 var obj1 = new MyClass(); 7 var obj2 = new MyClass(); 8 9 // both return the same value from the prototype 10 obj1.primitiveVal === 5; // true 11 obj2.primitiveVal === 5; // true 12 13 // obj2 get its own property (prototype remains unchanged) 14 obj2.primitiveVal = 10; 15 16 // obj1 still gets its value from the prototype 17 obj1.primitiveVal === 5; // true 18 obj2.primitiveVal === 10; // true 19 20 // both point to the array on the prototype, 21 // neither instance has its own array at this point 22 obj1.objectVal === obj2.objectVal; // true 23 24 // obj2 manipulates the prototype‘s array 25 obj2.objectVal.push(4); 26 27 // obj2‘s manipulation is reflected in obj1 since the array 28 // is shared by all instances from the prototype 29 obj1.objectVal.length === 4; // true 30 obj1.objectVal[3] === 4; // true 31 32 // only assignment of the property itself (no maniplation of object 33 // properties) creates an instance-specific property 34 obj2.objectVal = []; 35 obj2.objectVal === obj2.objectVal; // false
为了避免在实例之间无心的共享数组与对象属性,对象属性应该声明为null值且在构造函数中初始化:
1 declare(null, { 2 // not strictly necessary, but good pratice 3 // for readability to declare all properties 4 memberList: null, 5 roomMap: null, 6 7 constructor: function() { 8 // initializing these properties with values in the constructor 9 // ensures that they ready for use by other methods 10 // (and are not null or undefined) 11 this.memberList = []; 12 this.roomMap = {}; 13 } 14 });
查看dojo/_base/declare文档了解更多信息。
this.inherited
我们完全覆盖方法当然是有用的,但是有时需要保留原型链上同名方法的功能,这个时候this.inherited(arguments)就派上用场了。this.inherited(arguments)将调用父类的同名方法。考虑下面的代码:
1 // Define class A 2 var A = declare(null, { 3 myMethod: function() { 4 console.log("hello!"); 5 } 6 }); 7 8 // Define class B 9 var B = declare(A, { 10 myMethod: function() { 11 // Call A‘s myMethod 12 this.inherited(arguments); 13 console.log("World!"); 14 } 15 }); 16 17 // create an instance of B 18 var myB = new B(); 19 myB.myMethod(); 20 21 // would ouput: 22 // hello! 23 // World!
this.inherited方法可以在任何地方调用。有时候甚至想要子子函数或尾部调用它。这就是说,你不应在构造函数中调用它。
总结
declare函数是在Dojo Toolkit中构建类,重用类的关键。declare允许使用多继承构建复杂的类与多个属性和方法。所幸的是declare很好理解可以帮助开发者避免重复代码。
dojo/_base/declare资源
想学习关于delcare与构建类的更多细节?查看下面这些优秀的资源: