Creating Classes 创建类

时间:2023-03-08 15:38:03
Creating Classes 创建类

The dojo/_base/declare module is the foundation of class creation within the Dojo Toolkit. declare allows for multiple inheritance to allow developers to create flexible code and avoid writing the same code routines. Dojo, Dijit, and Dojox modules all use declare; in this tutorial, you'll learn why you should too.

dojo/_base/declare模块是dojo Toolkit定义的创建类的函数。decleare允许开发者使用多重集成机制,以避免编写重复代码。dojo,dijit以及dojox模块定义都是用declare。在本教程中,你将学习如何定义类。

Getting Started 开始

Make sure you have reviewed the concepts presented in the modules tutorial.

确定你已经了解了在模块教程中出现的相关概念。

Basic Dojo Class Creation with Dojo 使用dojo创建的最基本的类

The declare function is defined in the dojo/_base/declare module. declare accepts three arguments: className, superClass, and properties.

declare函数定义在dojo/_base/declare模块中,declare接受三个参数:className、SuperClass以及属性集合。

ClassName 类名

The className argument represents the name of the class, including the namespace, to be created. Named classes are placed within the global scope. The className can also represent the inheritance chain via the namespace.

className参数代表着类的名称,包含命名空间。命名的类位于全局作用域中。className也可以通过命名空间标识其归属及继承关系等。

Named Class 命名类
 / Create a new class named "mynamespace.MyClass"
declare("mynamespace.MyClass", null, { // Custom properties and methods here });

A class named mynamespace.MyClass is now globally available within the application.

一个命名为mynamespace.MyClass在应用中已经是一个全局变量。

Named classes should only be created if they will be used with the Dojo parser. All other classes should omit the className parameter.

当一个类需要dojo进行解析的时候,必须定义成命名类,其他的类可以省略className参数。

"Anonymous" Class 匿名类
 // Create a scoped, anonymous class
var MyClass = declare(null, { // Custom properties and methods here });

The MyClass is now only available within its given scope.

现在MyClass只有在定义它的作用域内可用。

SuperClass(es) 超类

The SuperClass argument can be null, one existing class, or an array of existing classes. If a new class inherits from more than one class, the first class in the list will be the base prototype, the rest will be considered "mixins".

超类参数可以为空、一个已经存在的类、或者多个存在的类组成的集合。如果一个新类在定义的时候集成了多个类,那么多个类中的第一个类将是基础原型类,其他的类被认为是混入类。

Class with No Inheritance 没有继承任何类的类
 var MyClass = declare(null, {

     // Custom properties and methods here

 });

null signifies that this class has no classes to inherit from.

null的意思就是该类没有继承其他任何类。

Class Inheriting from Another Class 类继承了其他类
 var MySubClass = declare(MyClass, {

     // MySubClass now has all of MyClass's properties and methods
// These properties and methods override parent's });

The new MySubClass will inherit MyClass's properties and methods. A parent class' method or property can be overridden by adding its key with a new definition within the third argument, which will be explained shortly.

新的MySubClass将会继承MyClass定义的属性和函数。

Class with Multiple Inheritance 多继承的类
 var MyMultiSubClass = declare([
MySubClass,
MyOtherClass,
MyMixinClass
],{ // MyMultiSubClass now has all of the properties and methods from:
// MySubClass, MyOtherClass, and MyMixinClass });

An array of classes signifies multiple inheritance. Properties and methods are inherited from left to right. The first class in the array serves as the base prototype, then the subsequent classes are mixins to that class.

一个类集合意味着多继承。从左到右,这些类的属性和函数将被继承。类集合中的第一个类承担基础原型的角色,后面其他的类担任混入类的角色。

If a property or method is specified in more than one inherited class, the property or method from the last inherited class is used.

如果属性或者方法在继承的不止一个类中都有定义,那么dojo将会认为最后一个定义是有效的。

Properties and Methods Object

The last argument of declare is an object containing methods and properties for the class prototype. Properties and methods provided via this argument will override their same namesake if inherited classes have the same properties.

最后一个参数是作为类的属性存在,该参数是一个对象,包含了一些属性和方法。如果该对象中定义的属性和方法和继承类中的有冲突,那么本属性和函数是有效的,继承类中无效。

Custom Properties and Methods
 / 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;
}
});

Example: Basic Class Creation and Inheritance

例子:使用继承,创建一个基础类

The following code creates a widget that inherits from dijit/form/Button:

下面的例子,我们创建了一个继承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);
}
});
});

From the snippet above, it's easy to conclude:

通过上面的小片段,我们得到以下结论:

  • The class' name is mynamespace.Button
  • The class may be referenced by the globally available mynamespace.Button or from the module's return value
  • The class inherits from dijit/form/Button (and thus Button's dependencies)
  • The class sets a few custom properties and methods
  • 类的名称是mynamespace.Button
  • 该类可以通过引用全局变量mynamespace.Button或者加载模型得到返回值使用
  • 该类集成了dijit/from/Button类(所以代码片段引用了button模块)
  • 该类自定义了一些属性和函数

Let's dig deeper into class creation with Dojo by learning about the constructor method.

让我们更深入的了解一下类的定义,下面我们看下构造函数:

The constructor Method 构造函数

One of the special class methods is the constructor method. The constructor method is fired upon class instantiation, executed in the scope of the new object. This means that the this keyword references the instance, not the original class. The constructor method also accepts any number of instance-specific arguments.

类中的一个特殊的函数就是构造函数了。当一个类被实例化成一个兑现之后,构造函数就会被触发。这也就意味着,this关键字引用的是该实例,而不是原始的类。构造函数可以接受任意数量的参数。

 // Create a new class
var Twitter = declare(null, {
// The default username
username: "defaultUser", // The constructor
constructor: function(args){
declare.safeMixin(this,args);
}
});

Take the following instance creation:

初始化类,得到一个实例:

 var myInstance = new Twitter();

The username used within this instance will be "defaultUser" since no specific settings were provided to the instance. To leverage the safeMixin method, provide a username parameter:

在没有做任何设置之前,实例中的username属性值为类中定义的默认值defaultUser。利用safeMixin函数,设置一个名为username的参数:

 var myInstance = new Twitter({
username: "sitepen"
});

Now the instance uses sitepen as the username setting!

现在该实例的username属性值已经变为sitepen了。

constructor: function(args){
declare.safeMixin(this,args);
该代码定义,就表明了该构造函数接受属性集合。declare.safeMixin is also useful in class creation and inheritance. As the API docs state:

declare.safeMixin在类的创建和继承中都十分有用,如API文档中描述:

This function is used to mix in properties like lang._mixin does, but it skips a constructor property and decorates functions like dojo/_base/declare does. It is meant to be used with classes and objects produced with dojo/_base/declare. Functions mixed in with declare.safeMixin can use this.inherited() like normal methods. This function is used to implement extend() method of a constructor produced with declare().

declare.safeMixin is the perfect ally when creating classes with numerous options。

Inheritance 继承

As stated above, inheritance is defined within the second argument of declare. Classes are mixed-in from left to right with each subsequent class' properties and methods getting priority over the previous if a property has already been defined. Take the following:

如上所述,我们在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
});

The result of the inherited class properties is:

继承后的类实例化的对象的属性值如下:

 // 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

It is important to have a clear understanding of prototypical inheritance. When a property is read from an object instance, the instance itself is first inspected to see if the property is defined on it. If not, the prototype chain is traversed and the value from the first object in the chain that has the property defined is returned. When a value is assigned to a property it is always on the object instance, never the prototype. The result of this is that all objects that share a common prototype will return the same value for a property defined on the prototype, unless the value has been set on the instance. This makes it easy to define default values for primitive data types (number, string, boolean) in your class declaration and update them on instance objects as needed. However, if you assign object values (Object, Array) to a property on the prototype, every instance will manipulate the same shared value. Consider the following:

有一点很重要,就是理解原型继承。当我们从一个对象实例上读取属性信息时,实例会先检查自己本身有没有定义该属性。如果没有,则会遍历原型链,直到找到第一个定义该属性的对象未知,并返回。当把一个值赋值给对象时,总是会把该值赋值给对象本身,而不是原型。这样的好处时,所有实例在初始化的时候都共享同一个原型对象,并得到同样的值,除非在对象本身定义了该属性。所以我们可以在原始类中定义一些默认值(字符串、数字和bool),当需要修改的时候,再在对象中修改。但如果类中定义属性是对象或数字,那么实例化的类将操作的是同一个对象。看下如下代码:

 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

To avoid inadvertently sharing arrays or objects among all instances, object properties should be declared with null values and initialized in the constructor function:

为了避免上述情况发生,即多个实例对象共享一个属性对象或数组,我们需要在属性初始化的时候置空,在构造函数中初始化这些属性。

 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 = {};
}
});

Refer to the dojo/_base/declare documentation for additional information.

this.inherited

While completely overriding methods is certainly useful, sometimes the constructor of each class up through the inheritance chain should be executed to preserve its original functionality. This is where the this.inherited(arguments) statement comes in handy. The this.inherited(arguments) statement calls the parent class' method of the same name. Consider the following:

虽然自动的覆盖同名属性或函数是很有用的,但有些时候,在函数中,需要调用父类的一些原本的函数。此时,就可以使用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!

The this.inherited method can be called at any time within the child class' code. There will be some cases where you will want to call inherited() in the middle of the child function, or even at the end. That said, you should not call it from within the constructor.

The this.inherited函数可以在子类中任何地方调用。

Conclusion

The declare function is the key to creating modular, reusable classes with the Dojo Toolkit. declare allows for complex class recreation with multiple inheritance and any number of properties and methods. Better yet is that declare is simple to learn and will allow developers to avoid repeating code.

dojo/_base/declare Resources

Looking for more detail about declare and class creation? Check out these great resources: