In this chapter, we will analyze a John Resig implementation of JavaScript inheritance -Simple JavaScript Inheritance。
John Resig asjQueryThe founder of the company is well-known. He is the author of "Pro JavaScript Techniques", and Resig will release a book "JavaScript Secrets" this fall, and I am looking forward to it.
Call method
The call method is very elegant:
Note: Class, extend, and _super in the code are all custom objects, which we will explain in detail in the subsequent code analysis.
var Person = ({ // init is the constructor init: function(name) { = name; }, getName: function() { return ; } }); // Employee class inherits from Person classvar Employee = ({ // init is the constructor init: function(name, employeeID) { // Call the constructor of the parent class in the constructorthis._super(name); = employeeID; }, getEmployeeID: function() { return ; }, getName: function() { // Call the parent class methodreturn "Employee name: " + this._super(); } }); var zhang = new Employee("ZhangSan", "1234"); (()); // "Employee name: ZhangSan"
To be honest, there are really no shortcomings in completing the goal - inheritance - of this series of articles. The method is as concise and clear as jQuery.
Code Analysis
For a beautiful call method, the internal implementation is indeed much more complicated, but these are also worth it - a person's thinking brings joyful smiles to countless programmers - hehe, it's a bit stingy.
But one of the codes did confuse me for a while:
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
I wrote an article in a blog a few days ago specifically explaining this issue. If you are interested, you can look forward.
// Create a context for self-executing anonymous functions to avoid introducing global variables(function() { // The initializing variable is used to indicate whether it is currently in the creation stage of the class. // - The prototype method init cannot be called during the creation stage of the class // - We have elaborated on this issue in the third article in this series // fnTest is a regular expression, and the possible values are (/\b_super\b/ or /.*/) // - The test of /xyz/.test(function() { xyz; }) is to detect whether the browser supports the test parameter as a function // - However, I tested IE7.0, Chrome 2.0, FF3.5, and this test all returned true. // - So I think this way to assign fnTest is correct in most cases: fnTest = /\b_super\b/; var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/; // Base class constructor // This here is window, so this whole piece of code opens a window to the outside world - = function() { }; // Inheritance method definition = function(prop) { // This place is very confusing. Do you remember what I mentioned in the second article in this series?// - What this specifically points to is not determined by definition, but depends on how this function is called// - We already know that extend is definitely called as a method, not as a constructor// - So here this does not point to Object, but Function (that is, Class), then it is the prototype object of the parent class// - Note: _super points to the prototype object of the parent class. We will encounter this variable many times in the subsequent codevar _super = ; // Complete inheritance by pointing the prototype of the child class to an instance object of the parent class// - Note: this is the base class constructor (i.e. Class)initializing = true; var prototype = new this(); initializing = false; // I think this code has been optimized by the author, so it reads very bluntly. I will explain it in detail laterfor (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && (prop[name]) ? (function(name, fn) { return function() { var tmp = this._super; this._super = _super[name]; var ret = (this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } // It can be seen from this place that Resig is very good at disguising.// - Using a local variable with the same name to overwrite the global variable is very confusing// - If you find it difficult to talk, you can use another name, such as function F() instead of function Class()// - Note: Class here is not the base class constructor defined in the outermost layerfunction Class() { // When instantiating the class, call the prototype method init if (!initializing && ) (this, arguments); } // The prototype of the subclass points to the instance of the parent class (the key to completing inheritance) = prototype; // Fix constructor pointing error = Class; // The subclass automatically obtains the extend method, pointing to the currently executing function = ; return Class; }; })();
Below I will interpret the for-in loop and replace the self-executed anonymous method with a local function, which will help us see the truth clearly:
(function() { var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/; = function() { }; = function(prop) { var _super = ; initializing = true; var prototype = new this(); initializing = false; // If the parent class and child class have the same name method, and this method (name) in the child class calls the parent class method through _super // - Redefine this method function fn(name, fn) { return function() { // 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 instance method name of the subclass, add another instance method_super, which points to the method of the parent class with the same name this._super = _super[name]; // Execute the method name of the subclass, note that this._super can call the parent class's method with the same name in the method body. var ret = (this, arguments); this._super = tmp; // Return the execution result return ret; }; } // Copy all attributes in the prop into the subclass prototype for (var name in prop) { // If there is a function with the same name in the prop and the parent class, and the _super method is used in this function, then special processing is performed on this method - fn // Otherwise, directly assign this method prop[name] to the prototype of the subclass if (typeof prop[name] === "function" && typeof _super[name] === "function" && (prop[name])) { prototype[name] = fn(name, prop[name]); } else { prototype[name] = prop[name]; } } function Class() { if (!initializing && ) { (this, arguments); } } = prototype; = Class; = ; return Class; }; })();
Having written this, do you think the implementation of Resig is very similar to the jClass we implemented step by step in Chapter 3. In fact, before writing this series of articles, I had a certain understanding of implementations such as prototype, mootools, extjs, jQuery-Simple-Inheritance, and Crockford-Classical-Inheritance, and most of them have been used in actual projects. Implementing jClass in Chapter 3 also refers to the implementation of Resig, and I would like to express my gratitude to Resig.
Next, we will transform jClass into the same behavior as the Class here.
Our implementation
To transform the jClass we implemented in Chapter 3 into the current form written by John Resig is quite simple, you only need to modify two or three lines:
(function() { // Is it currently in the stage of creating a class var initializing = false; jClass = function() { }; = function(prop) { // If the object calling the current function (here is the function) is not Class, it is the parent class var baseClass = null; if (this !== jClass) { baseClass = this; } // The class created by this call (constructor) function F() { // If you are currently in the instantiation class, 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 = ; } (this, arguments); } } // If this class needs to be extended from other classes if (baseClass) { initializing = true; = new baseClass(); = F; initializing = false; } // The newly created class automatically attaches the extend function = ; // Overwrite the function of the same name in the parent class for (var name in prop) { if ((name)) { // If this class inherits from the parent class baseClass and the parent class prototype has the same name function name if (baseClass && typeof (prop[name]) === "function" && typeof ([name]) === "function" && /\b_super\b/.test(prop[name])) { // Redefine the function name - // First set this._super in the function context pointing to the function with the same name in the parent class prototype // Then call the function prop[name] to return the function result // Note: The self-executing function here creates a context, which returns another function. // This function can apply variables in this context, which is the closure. // This is a common technique used in JavaScript framework development. [name] = (function(name, fn) { return function() { this._super = [name]; return (this, arguments); }; })(name, prop[name]); } else { [name] = prop[name]; } } } return F; }; })(); // Renovated jClass var Person = ({ init: function(name) { = name; }, getName: function(prefix) { return prefix + ; } }); var Employee = ({ init: function(name, employeeID) { // Call the parent class method this._super(name); = employeeID; }, getEmployeeIDName: function() { // Note: We can also call other functions in the parent class this way var name = this._superprototype.(this, "Employee name: "); return name + ", Employee ID: " + ; }, getName: function() { // Call the parent class method return this._super("Employee name: "); } }); var zhang = new Employee("ZhangSan", "1234"); (()); // "Employee name: ZhangSan" (()); // "Employee name: ZhangSan, Employee ID: 1234"
This article has been accepted. There is a series of articles below, you can read it