In this chapter, we will analyze John Resig's implementation of JavaScript Inheritance-Simple JavaScript Inheritance.
John Resig is well known as the founder of jQuery. It is the author of "Pro JavaScript Techniques", and Resig will launch a book "JavaScript Secrets" this fall.
Call Method
The call method is very elegant:
Note: The Class, extend, and _ super in the Code are all custom objects. We will explain them in the code analysis below.
Var Person = Class. extend ({// init is the constructor init: function (name) {this. name = name ;}, getName: function () {return this. name ;}}); // The Employee class inherits var Employee = Person from the Person class. extend ({// init is the constructor init: function (name, employeeID) {// call the constructor of the parent class in the constructor this. _ super (name); this. employeeID = employeeID;}, getEmployeeID: function () {return this. employeeID;}, getName: function () {// call the parent class method return "Employee name:" + this. _ super () ;}}); var zhang = new Employee ("ZhangSan", "1234"); console. log (zhang. getName (); // "Employee name: ZhangSan"
To be honest, there are really no disadvantages for accomplishing the objective-inheritance-of this series of articles. The method is as concise and clear as jQuery.
Code Analysis
For a beautiful call method, internal implementation is indeed a lot more complicated, but it is also worth it-a person's thinking brings a smile to countless programmers-Hey hey, it's a bit cool.
However, some of the Code does confuse me for a while:
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
I wrote an article about this issue in my blog a few days ago. If you are interested, I can go over it.
// Create a context for the Self-executed anonymous function to avoid introducing the global variable (function () {// initializing variable to indicate whether the class is currently being created, //-during the class creation stage, the prototype method init cannot be called. // we have elaborated on this issue in the third article in this series. // fnTest is a regular expression., the possible value is/\ B _super \ B/or /. */) //-to/xyz /. test (function () {xyz;}) is used to check whether the browser supports the test parameter as a function. //-however, I tested IE7.0, Chrome2.0, and FF3.5, true is returned for all tests. //-So I think it is also true to assign a value to fnTest in most cases: fnTest =/\ B _super \ B/; var initializing = false, fnTest =/xyz /. test (function () {xyz ;})? /\ B _super \ B /:/. * // The base class constructor // this is a window, so the entire code section opens a window-window to the outside world. class this. class = function () {}; // inherit the Class defined by the method. extend = function (prop) {// This place is very confusing, do you still remember what I mentioned in the second article of this series? //-what does this point, it depends on how this function is called. //-we already know that extend must be called as a method instead of a constructor. //-so here this is not an Object, but Function (that is, Class), then this. prototype is the prototype object of the parent class //-Note: _ super points to the prototype object of the parent class. We will encounter this variable var _ super = this multiple times in the code below. pro Totype; // inherit by pointing the subclass prototype to an instance object of the parent Class //-Note: this is the base Class Constructor (that is, Class) initializing = true; var prototype = new this (); initializing = false; // I think this code has been optimized by the author, so it is very hard to read, I will explain it later for (var name in prop) {prototype [name] = typeof prop [name] = "function" & typeof _ super [name] = "function" & fnTest. test (prop [name])? (Function (name, fn) {return function () {var tmp = this. _ super; this. _ super = _ super [name]; var ret = fn. apply (this, arguments); this. _ super = tmp; return ret ;};} (name, prop [name]): prop [name] ;}// this area can be seen, resig is a good disguise. // It is confusing to use a local variable with the same name to overwrite the global variable. // if you think of a hidden port, you can use another name, for example, function F () is used to replace function Class () //-Note: The Class here is not the base Class defined in the outermost layer. The constructor function Class () {// call the prototype method init if (! Initializing & this. init) this. init. apply (this, arguments);} // prototype of the subclass points to the instance of the parent Class (the key to inheritance) Class. prototype = prototype; // corrected the constructor pointing to the wrong Class. constructor = Class; // subclass automatically obtains the extend method, arguments. callee points to the currently executed function Class. extend = arguments. callee; return Class ;};})();
Next I will interpret the for-in loop and replace the self-executed anonymous method with a local function, which helps us to see the truth:
(Function () {var initializing = false, fnTest =/xyz/. test (function () {xyz ;})? /\ B _super \ B /:/. */; this. class = function () {}; Class. extend = function (prop) {var _ super = this. prototype; initializing = true; var prototype = new this (); initializing = false; // if the parent class and the subclass have a method of the same name, and this method (name) in the subclass) if _ super calls the parent class method //-then the function fn (name, fn) {return function () {// is redefined to protect the instance method _ super. // I personally think this place is unnecessary, because this. _ super will be redefined every time such a function is called. Var tmp = this. _ super; // when executing the subclass instance method name, add another instance method _ super, which points to the same name method of the parent class this. _ super = _ super [name]; // Method name of the execution subclass. Note that this is in the method body. _ super can call the method var ret = fn with the same name as the parent class. apply (this, arguments); this. _ super = tmp; // return the execution result return ret;} // copy all attributes in prop to the subclass prototype for (var name in prop) {// If a function with the same name exists in the prop and parent classes, and the _ super method is used in this function, this method is specially processed-fn // otherwise, this method prop [name] is directly assigned to the prototype if (typeof prop [name] = "function "&& Typeof _ super [name] === "function" & fnTest. test (prop [name]) {prototype [name] = fn (name, prop [name]);} else {prototype [name] = prop [name];} function Class () {if (! Initializing & this. init) {this. init. apply (this, arguments) ;}} Class. prototype = prototype; Class. constructor = Class; Class. extend = arguments. callee; return Class ;};})();
Do you think the implementation of Resig is similar to the jClass we implement step by step in Chapter 3. Before writing this series of articles, I have some knowledge about prototype, mootools, extjs, jQuery-Simple-Inheritance, Crockford-Classical-Inheritance, most of them have been used in actual projects. In chapter 3, the implementation of jClass also referred to the implementation of Resig. I would like to express my gratitude to Resig.
Next, we will transform jClass into the Class with the same behavior.
Our implementation
Transforming the jClass we implemented in Chapter 3 into the form written by John Resig is quite simple. You only need to modify the two or three lines:
(Function () {// whether it is currently in the stage of creating a class var initializing = false; jClass = function () {}; jClass. extend = function (prop) {// if the object that calls the current function (function here) is not a Class, it is the parent Class var baseClass = null; if (this! = JClass) {baseClass = this;} // The class (constructor) function F () created in this call {// if the class is currently in the stage of instantiation, call the init prototype function if (! Initializing) {// if the parent class exists, the baseprototype of the Instance Object points to the prototype of the parent class. // This provides a way to call the parent class method in the instance object if (baseClass) {this. _ superprototype = baseClass. prototype;} this. init. apply (this, arguments) ;}}// if this class needs to be extended from other classes if (baseClass) {initializing = true; F. prototype = new baseClass (); F. prototype. constructor = F; initializing = false;} // The extend function F is automatically attached to the newly created class. extend = arguments. callee; // override the Same name function of the parent class for (var name in prop) {If (prop. hasOwnProperty (name) {// if this class inherits from the parent class baseClass and the parent class prototype contains the same name function name if (baseClass & typeof (prop [name]) === "function" & typeof (F. prototype [name]) = "function" & amp;/\ B _super \ B /. test (prop [name]) {// redefine the function name-// first set this in the function context. _ super points to the function with the same name in the parent class prototype // then calls the function prop [name] and returns the function result. // Note: The self-executed function creates a context, this context returns another function, // This function can apply the variables in this context, this is the Closure (Closure ). // This is a common technique in JavaScript framework development. F. prototype [name] = (function (name, fn) {return function () {this. _ super = baseClass. prototype [name]; return fn. apply (this, arguments) ;};} (name, prop [name]);} else {F. prototype [name] = prop [name] ;}} return F ;}}) (); // transformed jClass var Person = jClass. extend ({init: function (name) {this. name = name ;}, getName: function (prefix) {return prefix + this. name ;}}); var Employee = Person. extend ({init: function (name, employeeID) {// call the method of the parent class this. _ super (name); this. employeeID = employeeID;}, getEmployeeIDName: function () {// note: we can also call other functions in the parent class var name = this. _ superprototype. getName. call (this, "Employee name:"); return name + ", Employee ID:" + this. employeeID;}, getName: function () {// call the parent class method return this. _ super ("Employee name:") ;}}); var zhang = new Employee ("ZhangSan", "1234"); console. log (zhang. getName (); // "Employee name: ZhangSan" console. log (zhang. getEmployeeIDName (); // "Employee name: ZhangSan, Employee ID: 1234"
Just cool!