Javascript inheritance (6)

Source: Internet
Author: User

In this chapter, we will analyze the implementation of JavaScript inheritance in prototypejs. Prototypejs is the earliest JavaScript class library. It can be said that it is the originator of the Javascript class library. This is the first JavaScript class library I came into contact with a few years ago, so prototypejs has a broad mass base.
However, in prototypejs, the inheritance implementation was quite simple,Source codeJust a few lines. Let's take a look.

Inheritance implementation in early prototypejs

Source code:

VaR class = {// class. create returns only another function. During execution, the prototype method initialize create: function () {return function () {This. initialize. apply (this, arguments) ;}}; // extended object of the object. extend = function (destination, source) {for (VAR property in source) {destination [property] = source [property] ;}return destination ;};

Call method:

VaR person = Class. create (); person. prototype = {initialize: function (name) {This. name = Name ;}, getname: function (prefix) {return prefix + this. name ;}}; var Employee = Class. create (); employee. prototype = object. extend (new person (), {initialize: function (name, employeeid) {This. name = Name; this. employeeid = employeeid;}, getname: function () {return "employee name:" + this. name ;}}); var Zhang = new employee ("zhangsan", "1234"); console. log (Zhang. getname (); // "employee name: zhangsan"

It is very primitive, right? In the subclass function, there is no way to call the parent function.

 

Inheritance implementation after prototypejs 1.6

first, let's look at the call method:

 // use the class. create creates a new class var person = Class. create ({// initialize is the constructor initialize: function (name) {This. name = Name ;}, getname: function (prefix) {return prefix + this. name ;}}); // class. the first parameter of create is the parent class var Employee = class to be inherited. create (person, {// you can set the first parameter of the subclass function to $ super to reference the Same Name function of the parent class. // It is relatively creative, but the internal implementation should be complicated, at least one closure should be used to set $ super context. This points to the current object initialize: function ($ super, name, employeeid) {$ super (name); this. employeeid = employeeid;}, getname: function ($ super) {return $ super ("employee name:") ;}}); var Zhang = new employee ("zhangsan ", "1234"); console. log (Zhang. getname (); // "employee name: zhangsan" 

here we extract the inheritance implementation in prototypejs 1.6.0.3 separately. Those who do not want to reference the entire prototype Library but only want to use prototype inheritance, you can copy the following Code and save it as a JS file.

VaR prototype = {emptyfunction: function () {}}; var class = {create: function () {var parent = NULL, properties = $ A (arguments); If (object. isfunction (properties [0]) parent = properties. shift (); function Klass () {This. initialize. apply (this, arguments);} object. extend (Klass, class. methods); Klass. superclass = parent; Klass. subclasses = []; If (parent) {var subclass = function () {}; subclass. Prototype = parent. prototype; Klass. prototype = new subclass; parent. subclasses. push (Klass) ;}for (VAR I = 0; I <properties. length; I ++) Klass. addmethods (properties [I]); If (! Klass. prototype. initialize) Klass. prototype. initialize = prototype. emptyfunction; Klass. prototype. constructor = Klass; return Klass ;}}; class. methods = {addmethods: function (source) {var ancestor = This. superclass & this. superclass. prototype; var properties = object. keys (source); If (! Object. keys ({tostring: true }). length) properties. push ("tostring", "valueof"); For (VAR I = 0, length = properties. length; I <length; I ++) {var property = properties [I], value = source [property]; If (ancestor & object. isfunction (value) & value. argumentnames (). first () = "$ super") {VaR method = value; value = (function (m) {return function () {return ancestor [M]. apply (this, arguments) };} (PR Operty ). wrap (method); value. valueof = method. valueof. BIND (method); value. tostring = method. tostring. BIND (method);} This. prototype [property] = value;} return this ;}}; object. extend = function (destination, source) {for (VAR property in source) Destination [property] = source [property]; return destination ;}; function $ A (iterable) {If (! Iterable) return []; If (iterable. toarray) return iterable. toarray (); var length = iterable. length | 0, Results = new array (length); While (length --) Results [length] = iterable [length]; return results;} object. extend (object, {keys: function (object) {var keys = []; for (VAR property in object) keys. push (property); Return keys;}, isfunction: function (object) {return typeof object = "function "; }, Isundefined: function (object) {return typeof object = "undefined" ;}}); object. extend (function. prototype, {argumentnames: function () {var names = This. tostring (). match (/^ [\ s \ (] * function [^ (] * \ ([^ \)] *) \)/) [1]. replace (/\ s +/g ,''). split (','); Return names. length = 1 &&! Names [0]? []: Names;}, BIND: function () {If (arguments. length <2 & object. isundefined (arguments [0]) return this; VaR _ method = This, argS = $ A (arguments), object = args. shift (); return function () {return _ method. apply (object, argS. concat ($ A (arguments) ;}}, wrap: function (wrapper) {VaR _ method = This; return function () {return wrapper. apply (this, [_ method. BIND (this)]. concat ($ A (arguments) ;}}); object. extend (array. prototype, {First: function () {return this [0] ;}});

 

First, we need to explain the definition of some methods in prototypejs.

  • Argumentnames: Get the parameter array of the Function

    Function Init ($ super, name, employeeid) {} console. Log (init. argumentnames (). Join (","); // "$ super, name, employeeid"
  • BIND: bind the context of the function this to a new object (usually the first parameter of the function)
    VaR name = "window"; var P = {name: "Lisi", getname: function () {return this. name ;}}; console. log (P. getname (); // "Lisi" console. log (P. getname. BIND (window) (); // "window"
  • Wrap: Use the currently called function as the first parameter of the wrapper function of the package.
    VaR name = "window"; var P = {name: "Lisi", getname: function () {return this. name ;}}; function wrapper (originalfn) {return "Hello:" + originalfn ();} console. log (P. getname (); // "Lisi" console. log (P. getname. BIND (window) (); // "window" console. log (P. getname. wrap (wrapper) (); // "Hello: window" console. log (P. getname. wrap (wrapper ). BIND (p) (); // "Hello: Lisi"

    There is a detour, right. It should be noted that both wrap and bind Call return functions. By grasping this principle, it is easy to see the essence.

 

after understanding these functions, let's parse the core content inherited by prototypejs. There are two important definitions: class. Extend and Class. Methods. addmethods.

VaR class = {create: function () {// if the first parameter is a function, VAR parent = NULL, properties = $ A (arguments); If (object. isfunction (properties [0]) parent = properties. shift (); // The definition function Klass () {This. initialize. apply (this, arguments);} // Add the prototype method class to the subclass. methods. addmethods object. extend (Klass, class. methods); // not only saves the reference of the parent class for the current class, but also records the reference of all subclass Klass. superclass = parent; Klass. subclasses = []; If (Parent) {// core code-if the parent class exists, implement prototype inheritance // This provides a new way to create a class without calling the constructor of the parent class. //-use an intermediate transitional class, this is the same purpose as we used the global initializing variable before, //-but the code is more elegant. VaR subclass = function () {}; subclass. prototype = parent. prototype; Klass. prototype = new subclass; parent. subclasses. push (Klass);} // The core code-if the subclass has the same method as the parent class, the special processing will be detailed later in for (VAR I = 0; I <properties. length; I ++) Klass. addmethods (properties [I]); If (! Klass. Prototype. initialize) Klass. Prototype. initialize = prototype. emptyfunction; // corrected the constructor pointing to the error Klass. Prototype. constructor = Klass; return Klass ;}};

let's take a look at what addmethods has done:

Class. methods = {addmethods: function (source) {// if the parent class exists, ancestor points to the prototype object var ancestor = this of the parent class. superclass & this. superclass. prototype; var properties = object. keys (source); // Firefox and chrome return 1, IE8 returns 0, so this place specially handles if (! Object. keys ({tostring: true }). length) properties. push ("tostring", "valueof"); // all attributes defined by the loop subclass prototype. For functions with the same name as the parent class, you must redefine for (VAR I = 0, length = properties. length; I <length; I ++) {// property is the property name, value is the property body (may be a function or an object) var property = properties [I], value = source [property]; // if the parent class exists and the current attribute is a function, and the first parameter of this function is $ super if (ancestor & object. isfunction (value) & value. argumentnames (). first () = "$ super") {VaR method = value; // The following three lines of code are the essence. The approximate meaning is: //-first, create an self-executed anonymous function and return another function, this function is used to execute functions with the same name as the parent class //-(because this is in a loop, we have repeatedly pointed out that the function in the loop references local variables) //-second, use the self-executed anonymous function as the first parameter of method (that is, corresponding to the parameter $ super) // However, the author of this part is a little confused, there is no need for such complexity. I will analyze this code in detail later. Value = (function (m) {return function () {return ancestor [M]. apply (this, arguments) };} (property ). wrap (method); value. valueof = method. valueof. BIND (method); // because we have changed the function body, we need to redefine the tostring method of the function. // when you call the tostring method of the function, the returned value is the original function definition body value. tostring = method. tostring. BIND (method);} This. prototype [property] = value;} return this ;}};

In the above code, I used to say "Get angry". It is not a foul expression to the author. I just think that the author has an important criterion for JavaScript (create a scope through self-executed anonymous functions) it is a bit overhead.

 
Value = (function (m) {return function () {return ancestor [M]. Apply (this, arguments) };}) (property). Wrap (method );

In fact, this code has the same effect as the following code:

 
Value = ancestor [property]. Wrap (method );

We can see more clearly by expanding the wrap function:

 
Value = (function (FN, wrapper) {VaR _ method = FN; return function () {return wrapper. apply (this, [_ method. BIND (this)]. concat ($ A (arguments) ;}) (ancestor [property], method );

We can see that we actually created a scope for the ancestor [property] function of the parent class through self-executed anonymous functions. The original author is the scope created for the property. The final results of the two statements are the same.

 

Implementation of prototypejs inheritance

It is not difficult to analyze so many things. There are so many concepts, and there is no way to express them. Next we will use the jclass we implemented in the previous chapters to implement prototypejs inheritance.

// Note: this is the Code implemented by ourselves similar to the prototypejs inheritance method. You can directly copy it and use it. // This method uses the function argumentnames (FN) defined in prototypejs) {var names = fn. tostring (). match (/^ [\ s \ (] * function [^ (] * \ ([^ \)] *) \)/) [1]. replace (/\ s +/g ,''). split (','); Return names. length = 1 &&! Names [0]? []: Names;} function jclass (baseclass, Prop) {// only accepts one parameter-jclass (PROP) if (typeof (baseclass) = "object ") {prop = baseclass; baseclass = NULL;} // The class (constructor) function f () created in this call {// if the parent class exists, the baseprototype of the Instance Object points to the prototype of the parent class. // This provides the way to call the parent class method in the instance object if (baseclass) {This. baseprototype = baseclass. prototype;} This. initialize. apply (this, arguments);} // if this class needs to be extended from other classes if (baseclass) {var middleclass = function () {}; middleclass. prototype = baseclass. prototype; F. prototype = new middleclass (); F. prototype. constructor = f;} // overwrite 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" & argumentnames (prop [name]) [0] === "$ super ") {// re-define the prototype method prop [name] //-there are many JavaScript skills in this method. If reading is difficult, see my previous JavaScript tips and tricks SeriesArticle //-For example, $ super encapsulates the call of the parent class method, however, the context Pointer Points to the Instance Object of the current subclass //-use $ super as the first parameter F for method calling. prototype [name] = (function (name, FN) {return function () {var that = This; $ super = function () {return baseclass. prototype [name]. apply (that, arguments) ;}; return fn. apply (this, array. prototype. concat. apply ($ super, arguments) ;};} (name, prop [name]);} else {f. prototype [name] = prop [name] ;}} return F ;};

The call method is the same as that of prototypejs:

VaR person = jclass ({initialize: function (name) {This. name = Name ;}, getname: function () {return this. name ;}}); var Employee = jclass (person, {initialize: function ($ super, name, employeeid) {$ super (name); this. employeeid = employeeid;}, getemployeeid: function () {return this. employeeid ;}, getname: function ($ super) {return "employee name:" + $ super () ;}}); var Zhang = new employee ("zhangsan ", "1234"); console. log (Zhang. getname (); // "employee name: zhangsan"

 

after studying this chapter, we have strengthened our confidence that inheritance in prototypejs forms can be easily implemented. In the following sections, we will gradually analyze the implementation inherited by JavaScript class libraries such as mootools and extjs.

Related Article

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.