Introduces a simple JavaScript class framework

  • 2020-06-19 09:43:44
  • OfStack

When Writing the ES1en-ES2en-ES3en JavaScript book1 book, I spent a considerable amount of time on the javascript inheritance system and in the process researched various schemes to simulate classical class inheritance. Among these technical solutions, I most recommend the implementation of base2 and Prototype.

From these solutions, it should be possible to extract a framework with its connotation. The framework should be simple, reusable, easy to understand and dependency free, among which simplicity and usability are the key points. Here is an example of how to use it:


var Person = Class. extend ( {
 init: function (isDancing ) {
  this. dancing = isDancing;
 },
 dance: function ( ) {
  return this. dancing;
 }
} );
var Ninja = Person.extend({
 init: function(){
  this._super( false );
 },
 dance: function(){
  // Call the inherited version of dance()
  return this._super();
 },
 swingSword: function(){
  return true;
 }
});
var p = new Person(true);
p.dance(); // => true
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true
// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class

A few notes:

The constructor must be simple (implemented through the init function), The newly defined analogy must be inherited from the existing class, All 'classes' inherit from the ancestor class: Class, so to create a new class, the class must be a subclass of Class, Most challenging point 1: Superclass overridden methods must be accessible (by configuring the context). In the above example, you can see that the init() and dance() methods of the Person parent are called via this. _super().

Satisfied with the results: structured the class definition, kept single 1 inheritance, and able to call superclass methods.

Simple class creation and inheritance

The following implementation (easy to read and annotated) is about 25 lines or so. Suggestions are welcome and appreciated.


/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
( function ( ) {
 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
 // The base Class implementation (does nothing)
 this.Class = function(){};
  
 // Create a new Class that inherits from this class
 Class.extend = function(prop) {
  var _super = this.prototype;
   
  // Instantiate a base class (but only create the instance,
  // don't run the init constructor)
  initializing = true;
  var prototype = new this();
  initializing = false;
   
  // Copy the properties over onto the new prototype
  for (var name in prop) {
   // Check if we're overwriting an existing function
   prototype[name] = typeof prop[name] == "function" &&
    typeof _super[name] == "function" && fnTest.test(prop[name]) ?
    (function(name, fn){
     return function() {
      var tmp = this._super;
       
      // Add a new ._super() method that is the same method
      // but on the super-class
      this._super = _super[name];
       
      // The method only need to be bound temporarily, so we
      // remove it when we're done executing
      var ret = fn.apply(this, arguments);    
      this._super = tmp;
       
      return ret;
     };
    })(name, prop[name]) :
    prop[name];
  }
   
  // The dummy class constructor
  function Class() {
   // All construction is actually done in the init method
   if ( !initializing && this.init )
    this.init.apply(this, arguments);
  }
   
  // Populate our constructed prototype object
  Class.prototype = prototype;
   
  // Enforce the constructor to be what we expect
  Class.prototype.constructor = Class;
  // And make this class extendable
  Class.extend = arguments.callee;
   
  return Class;
 };
})();

Among them, "initialize (initializing/don 't call init)" and "create _super method" are the most difficult. Next, I'll give you a brief introduction, so that you can better understand the implementation mechanism.

Initialize the

To illustrate the inheritance of the original form of a function, let's first look at the traditional implementation, in which the prototype attribute of a subclass points to an instance of the parent class. As shown below:



function Person ( ) { }
function Ninja ( ) { }
Ninja. prototype = new Person ( );
// Allows for instanceof to work:
(new Ninja()) instanceof Person

However, the challenging point here is that we only want the effect of 'instance or not (instatnceOf)' without having to instance 1 Person and call its constructor. To prevent this 1 point, set an bool parameter, initializing, in the code, whose value is true only when the parent class is instantiated and configured to the subclass's prototype property. The purpose of this approach is to distinguish between real instantiation and design inheritance and call the init method on real instantiation:


if ( !initializing )
 this.init.apply(this, arguments);

It's worth noting in particular that this distinction is necessary because you can run fairly expensive code in init functions (such as connecting to the server, creating DOM elements, etc., which no one can predict).

Superclass method (Super Method)

When using inheritance, the most common requirement is that subclasses have access to superclass overridden methods. With this implementation, the final solution is to provide a temporary method (._super) that points to a superclass method and is only accessible in a subclass method.


var Person = Class. extend ( {
 init: function (isDancing ) {
  this. dancing = isDancing;
 }
} );
var Ninja = Person.extend({
 init: function(){
  this._super( false );
 }
});
var p = new Person(true);
p.dancing; // => true
var n = new Ninja();
n.dancing; // => false


Implementing this 1 feature requires several steps. First, we use extend to combine the basic Person instance (class instance, which we mentioned above in its construction) with the literal object (function arguments to Person.extend ()). During the merge process, a simple check is made: first check whether the property to be merged is a function, such as a function, and then check whether the superclass property to be overridden is also a function. If both of these checks are true, you need to prepare the _super method for this property.

Note that an anonymous closure (returning a function object) is created here to encapsulate the added super method. Based on the need to maintain the running environment, we should save the old ES90en. _super (whether it exists or not) for a reset after the function runs, which helps in unpredictable problems if you have the same name (you don't want to accidentally lose object Pointers).

Then, create a new _super method object that points only to the overridden method in the superclass. Thankfully, no changes or scope changes are made to _super, because the execution environment of the function automatically changes with the function call object (the pointer to this points to the superclass).

Finally, the method of the literal object is called, possibly using this. _super(), and the property _super is reset back to its original state after the method executes, and return exits the function.


There are many ways to achieve the same effect (I've seen super bundled into itself and accessed with ES107en.callee before), but I feel that this approach is the best way to achieve usability and simplicity.

Of the many javascript prototypes I have worked on, this is the only class inheritance implementation I have published to share with you. In my opinion, the clean code (easy to learn, easy to inherit, and less to download) needs to be discussed more, so this implementation is a good place to start for people learning javascript class construction and inheritance.


Related articles: