Implementation Principle Analysis of the dojo class Mechanism

Source: Internet
Author: User
Tags hasownproperty

Some time ago, I published an article on infoq Chinese site, introducing the basic usage of the dojo class mechanism. Some of my friends hope to have a better understanding of this part. This article will introduce the background knowledge of the dojo class mechanism, it involves the implementation principle of the dojo class mechanism and the source code analysis of some key methods. Of course, before that, I hope you can have a basic understanding of the use of JavaScript and dojo.

The class mechanism of dojo supports class declaration, inheritance, and calling of parent class methods. At the underlying implementation level, Dojo implements its class mechanism by operating the prototype chain, while class inheritance is used for implementation. It is worth mentioning that the dojo class mechanism allows multi-inheritance (note that only the first in the parent class list serves as the real parent class, the other is to add its attributes to the prototype chain of the subclass using the Mixin method). To solve the sequence problem of class methods when multiple inheritance occurs, dojo uses JavaScript to implement the C3 parent class linearity algorithm supported by Python and other multi-inheritance languages to implement linear inheritance. For more information about this algorithm, see here, in the subsequent analysis, we will briefly explain the implementation of this algorithm by dojo.

1. dojo class declaration Overview

The Code related to the dojo class declaration is located in the "/dojo/_ base/declare. js" file, and the definition class is implemented through the dojo. Declare method. The basic usage of this method has been described in this article about the dojo class mechanism. Now let's take a look at its implementation principles (in this Part of code analysis, I will introduce how to declare classes in Dojo as a whole, and I will introduce important details in later ):

// This is Dojo. definition of the declare method D. declare = function (classname, superclass, props) {// There are formatting parameter-related operations in front. Generally, the definition class will include all three parameters, they are // class name and parent class (which can be null, an array composed of a class or multiple classes) and the attributes and methods of the class to be declared // define a series of variables for the following use VaR proto, I, T, ctor, name, bases, chains, mixins = 1, parents = superclass; // process if (OPTs. call (superclass) = "[object array]") {// if the parent class parameter is an array, here it is multi-inheritance, use the C3 algorithm to process the parent class relationship // The resulting bases is an array, and the first element can identify the real parent class (that is, the first in the superclass parameter) // the cable in the array Other array elements are sorted inheritance chains. We will introduce the C3 algorithm bases = c3mro (superclass, classname); t = bases [0]; mixins = bases. length-T; superclass = bases [mixins];} else {// This branch is used to handle situations where no parent class or a single parent class exists, no more details} // The following are the prototype attributes and methods of the build class if (superclass) {for (I = mixins-1; -- I) {// traverse all classes that require Mixin. // note that, when multiple parent classes exist, only the first parent class is the real parent class, this parent class is instantiated for the first loop and recorded in the prototype chain, other classes that require Mixin // The parent class will set the superclass as an empty constructor when processing it later. Then, combine the parent class prototype chain // and instantiate proto = forcenew (supercl Ass); If (! I) {// after completing the last parent class, jump out of the loop break;} // mix in properties T = bases [I]; // obtain a parent class (T. _ meta? Mixown: mix) (PROTO, T. prototype); // combine the prototype chain // chain in new constructor ctor = new function; // declare a new function ctor. superclass = superclass; ctor. prototype = proto; // set the prototype chain // at this time, superclass points to this new function. When entering this cycle again, the instance // converts it to the ctor, instead of the Mixin parent class superclass = Proto. constructor = ctor ;}} else {proto ={};}// the method (and attributes) obtained above and the method (and attributes) of the class to be declared) merge safemixin (PROTO, props );............ // Collect information about the chained call, which will be detailed later in for (I = mixins-1; I; -- I) {// intentional assignment t = bases [I]. _ Meta; If (T & T. chains) {chains = mix (chains ||{}, T. chains) ;}} if (PROTO ["-chains-"]) {chains = mix (chains ||{}, proto ["-chains-"]);} // The final constructor is constructed based on the chain call information and parent class information collected above, which is detailed in the following article. t =! Chains |! Chains. hasownproperty (cname); bases [0] = ctor = (chains & chains. constructor = "Manual ")? Simpleconstructor (bases): (bases. Length = 1? Singleconstructor (props. constructor, T): chainedconstructor (bases, t); // many attributes are added to this constructor, the Ctor is used for chained calling and calling the parent class method. _ meta = {bases: bases, hidden: props, Chains: chains, parents: parents, ctor: props. constructor}; ctor. superclass = superclass & superclass. prototype; ctor. extend = extend; ctor. prototype = proto; Proto. constructor = ctor; // For dojo. all instances of the declare method declaration class have the following tool Methods: PROTO. getinherited = Ge Tinherited; Proto. inherited = inherited; Proto. isinstanceof = isinstanceof; // You must register if (classname) {Proto. declaredclass = classname; D. setobject (classname, ctor);} // process the methods that call the parent class in a chain. In fact, the method is overwritten. In this article, we will detail if (chains) {for (name in chains) {If (PROTO [name] & typeof chains [name] = "string" & name! = Cname) {T = proto [name] = chain (name, bases, chains [name] = "after"); T. nom = Name ;}} return ctor; // function };

The above briefly introduces the overall process of the dojo Declaration class, but some key details such as the C3 algorithm and chained call will be further introduced later.

2. C3 algorithm implementation

Through previous articles and the above analysis, we know that the class declaration of dojo supports multi-inheritance. When dealing with multiple inheritance, we have to deal with how to construct the inheritance chain. The more practical problem is that if multiple parent classes have methods of the same name, when calling the parent class method, what rules should be used to determine which parent class to call? To solve this problem, Dojo implements the C3 parent class linearity method, and rationally sorts multiple parent classes, thus solving this problem perfectly.

To learn about the inheritance chain, let's look at a simple example:

dojo.declare("A",null);dojo.declare("B",null);dojo.declare("C",null);dojo.declare("D",[A, B]);dojo.declare("E",[B, C]); dojo.declare("F",[A, C]); dojo.declare("G",[D, E]);

In the above Code, several classes are declared. The inheritance order of G obtained through the C3 algorithm should be like this: G-> E-> C-> D-> B->, only in this order can we ensure that the class definition and dependency are correct. Let's take a look at how the C3 algorithm is implemented:

Function c3mro (bases, classname) {// defines a series of variables var result = [], roots = [{CLS: 0, refs: []}], namemap = {}, clscount = 1, L = bases. length, I = 0, J, Lin, base, top, proto, REC, name, refs; // in this loop, build the dependencies between the parent class (that is, the parent class may depend on other classes) for (; I <L; ++ I) {base = bases [I]; // obtain the parent class ............ // In the classes declared by dojo, There Is A _ meta attribute that records the parent class information. Here, we can get the inheritance chain lin = base. _ Meta that contains itself in? Base. _ meta. bases: [base]; Top = 0; For (j = Lin. length-1; j> = 0; -- j) {// traverse the elements in the inheritance chain. Note that the processing here is reverse, that is, starting from the bottom layer, until the top of the chain proto = LiN [J]. prototype; If (! Proto. hasownproperty ("declaredclass") {Proto. declaredclass = "uniqname _" + (counter ++);} name = Proto. declaredclass; // namemap records the classes used in map mode and does not repeat if (! Namemap. hasownproperty (name) {// each class has such a structure. refs is particularly important and records the reference of the dependency class namemap [name] = {count: 0, refs: [], CLS: Lin [J]}; ++ clscount;} rec = namemap [name]; If (top & Top! = REC) {// when the condition is met, it means that the current class depends on the class referenced by top at this time, that is, the previous element REC of the chain. refs. push (top); ++ top. count ;}top = REC; // top points to the current class and starts the next loop. count; roots [0]. refs. push (top); // put a parent class in the root reference after processing is complete} // so far, we have established the dependency between parent class elements, the following link must be correctly processed while (roots. length) {Top = roots. pop (); // puts the dependent class into the result set. push (top. CLs); -- clscount; // optimization: follow a single-linked chain while (Refs = top. refs, refs. length = 1) {// if the current class depends on a parent class, process this dependency. Chain Top = refs [0]; If (! Top | -- top. count) {// note that there is a top. the Count variable is used to record the number of times this class has been referenced. // if the value is greater than zero after the value is reduced by one, it indicates that there is a reference later and will not be processed at this time, that is why G-> E-> C-> B is not found in the previous example. Top = 0; break;} result. push (top. CLs); -- clscount;} If (top) {// if multiple branches are depended on, the dependent classes are placed in the roots. This Code only inherits from multiple branches, // The for (I = 0, L = refs. length; I <L; ++ I) {Top = refs [I]; If (! -- Top. count) {roots. push (top) ;}}}if (clscount) {// if the value of clscount is greater than 1 after the above processing is complete, err ("can't build consistent linearization", classname);} // After building the inheritance chain, identify the location of the real parent class in the chain, returns the first element of the array base = bases [0]; Result [0] = base? Base. _ meta & base = Result [result. Length-base. _ meta. Bases. Length]? Base. _ meta. Bases. Length: 1: 0; return result ;}

Through the above analysis, we can see that this algorithm is quite complicated to implement. If you are interested in it, we suggest you add a breakpoint for debugging and analysis based on the above example. The author of dojo has used less than 100 lines of code to implement such powerful functions. There are many design ideas worth learning.

 

3. Implementation of chain Constructor

In the first part of code analysis, we have seen the code that defines the constructor, as follows:

bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) :                     (bases.length == 1 ? singleConstructor(props.constructor, t) : chainedConstructor(bases, t));

This method is very important for understanding the dojo class mechanism. In the previous article, we learned that by default, if the classes declared by dojo have an inheritance relationship, the constructor of the parent class is automatically called, in addition, the constructor of the parent class is called in the order of the inheritance chain. However, from version 1.4, Dojo provides the option to manually set the constructor call. The above Code involves three methods of the dojo Declaration class. If the class does not have a parent class, singleconstructor is called. If there is a parent class, chainedconstructor is called by default, if you have manually set the constructor, simpleconstructor is called. To enable this option, you only need to add the constructor Declaration of the chains when declaring the class.

For example, when we define the com. levinzhang. Employee class inherited from Com. levinzhang. person, we can do this:

dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{       "-chains-": {              constructor:"manual"       },…………}

After the preceding code is added. levinzhang. when the employee instance is used, the constructor of all parent classes will no longer be called. However, we can use the inherited method to explicitly call the parent class method.

The preceding three methods are not all described, but the core implementation of chainedconstructor is described as follows:

 

Function chainedconstructor (bases, ctorspecial) {return function () {// some preparations are made before this time. No details are provided. // all the parent classes are found, call its constructor for (I = L-1; I> = 0; -- I) {f = bases [I]; M = f. _ Meta; F = m? M. ctor: F; // obtain the constructor of the parent class if (f) {// call the method F. Apply (this, preargs? Preargs [I]: a) ;}// note that after the constructor is executed, the method named postscript is executed, this method is // The key life cycle method of dijit component implementation of dojo F = this. postscript; If (f) {f. apply (this, argS );}};}

 

4. Implementation of calling the parent class Method

When declaring a dojo class, the methods to call the parent class are generally implemented by using the inherited method. However, since version 1.4, Dojo supports chained call of all parent class methods, some AOP concepts are introduced. We will introduce these two methods respectively.

1) Use the inherited method to call the parent class Method

In the previous article, we once introduced that you can call it by using inherited in the class. Here we will go deep into the inherited and look at its implementation principles. Because inherited supports calling the general method and constructor method of the parent class, the two are slightly different. We pay attention to the process of calling the general method.

Function inherited (ARGs, A, F ){............ // Some parameters are processed before this. If (name! = Cname) {// not the constructor if (Cache. C! = Caller) {// some codes between them solve the problem of determining the caller, that is, determine where to start searching for the parent class} // find the method with the same name as the parent class in order base = bases [++ POS]; If (base) {proto = base. prototype; If (base. _ meta & proto. hasownproperty (name) {f = proto [name]; // found this method} else {// if the corresponding method is not found, the OPF = op [name] will be searched forward based on the inheritance chain; do {proto = base. prototype; F = proto [name]; If (F & (base. _ meta? Proto. hasownproperty (name): F! = OPF) {break;} while (base = bases [++ POS]); // intentional assignment }}f = base & F | op [name];} else {// here is the constructor for calling the parent class} If (f) {// After the method is found, run return a = true? F: f. Apply (this, A | ARGs );}}

2) chained call of the parent class Method

This is a new feature from dojo 1.4. If you want to execute the parent class method in a certain order when executing a method, you only need to declare it in the-chains-attribute when defining the class.

dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{"-chains-": {     sayMyself:    "before"       },……}

After the preceding Declaration is added, it means that when the saymyself method is called, the same name method is called first, then, follow the inheritance chain to call methods of the same name of all parent classes. We can also replace the value "before" with "after", and the execution order will be the opposite. The Methods declared in-chains-attributes are specially processed during class definition, as we can see in Chapter 1:

 if(chains){                     for(name in chains){                           if(proto[name] && typeof chains[name] == "string" && name != cname){                                  t = proto[name] = chain(name, bases, chains[name] === "after");                                  t.nom = name;                           }                     }              }

We can see that all the methods declared in-chains-are replaced with the return value of the chain method, which is also relatively simple. The source code is as follows:

Function chain (name, bases, reversed) {return function () {var B, M, F, I = 0, step = 1; if (reversed) {// determination order, that is, "after" or "before", corresponding to the different starting point and direction of the loop I = bases. length-1; step =-1;} For (; B = bases [I]; I + = step) {// query the parent class M = B in sequence. _ Meta; // find the method F = (M? M. Hidden: B. Prototype) [name]; If (f) {// execute F. Apply (this, arguments );}}};}
 

5. Implementation of tool methods and attributes such as isinstanceof and declaredclass

In addition to the inherited method mentioned above, Dojo implements some tool methods and attributes when implementing class functions. Here we introduce a method isinstanceof and a property declaredclass. The isinstanceof method is used to determine whether an object is an instance of a class. The declaredclass attribute obtains the name of the declared class corresponding to an object.

Function isinstanceof (CLS) {// obtain all classes of the Instance Object Inheritance chain var bases = This. constructor. _ meta. bases; // traverse all classes to see if they are equal to the passed classes (VAR I = 0, L = bases. length; I <L; ++ I) {If (bases [I] === CLs) {return true ;}} return this instanceof CLS ;}

The implementation of the declaredclass attribute is simple, but an attribute is added to the Declaration class prototype. The instance object of the class can access this attribute to get the name of the declared class. This code is in the dojo. Declare method:

if(className){                     proto.declaredClass = className;                     d.setObject(className, ctor);              }

In the process of implementing the class mechanism in Dojo, some internal methods are worth learning, such as forcenew and safemixin, this ensures efficient code execution and can be further studied by interested users.

6. Summary and consideration

1) dojo supports multiple inheritance methods in implementing the class mechanism, which is rarely implemented in other JavaScript class libraries. It is also difficult to implement multiple inheritance using the native JavaScript syntax. At this point, the functions of the dojo class mechanism are indeed powerful enough. However, multi-inheritance increases the coding difficulty and requires developers to organize classes;

2) when calling the parent class method in a chain, we can see that dojo introduces a lot of AOP concepts. In version 1.7, there will be a separate module to provide AOP-related support, we will continue to provide similar features;

3) in the code of dojo, multiple methods are replaced, such as chained method calling and event binding. This design concept deserves our attention and learning;

4) many internal attributes, such as _ Meta and bases, are used. These metadata plays a crucial role in implementing complex class mechanisms. During source code analysis, we can pay attention to it and use it for reference if we want to implement similar functions.

 

Exploring the implementation principle of the class library is a good way to improve your coding level, basically, each line of core code of a class library similar to dojo has its own design idea (and of course it cannot be blindly worshipped). Every time you read and explore it, you will find something and learn about it, of course, there will certainly be self-righteousness or errors in it. I would like to study with my friends who have read this article and welcome criticism and correction.

 

 

References:

Http://docs.dojocampus.org/

Http://blog.csdn.net/dojotoolkit/

Http://dojotoolkit.org/

 

Author information: Zhang weibin, pay attention to Enterprise Java Development and RIA technology, personal blog: http://lengyun3566.iteye.com, Weibo: http://weibo.com/zhangweibin1981

 

Statement: 
This article has been first published on the infoq Chinese site. All Rights Reserved. The original Article is "implementation Principle Analysis of dojo class mechanism". If you need to reprint it, please attach this statement. Thank you.
Infoq Chinese site is an online independent community for mid-and high-end technical personnel ,. net, Ruby, SOA, agility, architecture and other fields to provide timely and in-depth information, high-end technology conferences such as qcon, offline technology exchange activities qclub, free mini book download such as architect, etc..

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.