npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

practical-inheritance

v1.1.2

Published

A true prototypal inheritance pattern that effectively replaces the classical constructor pattern

Downloads

11

Readme

The practical inheritance pattern has been designed to standarize the inheritance pattern used within iai modules. It has been published independently, attached to the following paper:

An alternative to the constructor pattern

Principles

Simplicity & expressiveness

The object declarations should not be verbose and must be expresive. Vanilla javascript is enough expresive but sometimes is verbose. That said, sometimes vanilla javascript produces the most simple and most descriptive code, so encourage to not use an utility function when it does not provide benefits for readability of code.

Targets

#1: Forget the concept of classes.

Everything is a instance in javascript, so talking about classes, even in quotes, is a mistake. There is no excuse. This target leads to the second:

#2: Completely eliminate the use of the new keyword.

The use of the new keyword produces code that looks like a class instantiation, confusing readers. In addition, the use of new is not combinable with the ECMAScript Function.prototype functionalities, specially call and apply, and breaks possible chained calls.

~~constructors~~ builders

Research about creational design patterns and cross out definitions that refer specifically to the concept of classes. Prototype and Builder are the patterns that do not rely specifically on the class concept by definition. In fact, JavaScript implements both natively:

  • The prototype pattern, through the native Object. See on the ECMA 5.1 specification:

    • prototype definition

    • Object.create alghoritm

          // prototype is a fully initialized instance of Object.
          var prototype = {
            // ...
          };
          // create a new object with specified prototype
          var object = Object.create( prototype );
  • The builder pattern, allowing the use of a function as an object's ~~constructor~~ when invoked with the new keyword (aka. constructor pattern). As said previously, the use of the new keyword should be completely eliminated so this pattern should be avoided.

      // the function builder separates object construction from its representation (aka instance)
      function builder(){
        // ...
      }
      builder.prototype = {
        // ...
      };
      // builder can create different representations with the same construction process
      var object = new builder();

~~prototypal~~ ~~classical~~ practical inheritance

As said, the tools to implement a prototypal inheritance pattern, where object instances are created from other object instances, are natively bundled within ECMAScript specification. Object.create is the way to create a new object with the specified prototype. The challengue is a pattern that efficiently replaces the ~~constructor~~ builder pattern on the task of initialize a newly created object with the specified prototype. The solution is quite simple, and surprisingly somewhere between the constructor pattern and the prototypal inheritance pattern: use a function that creates a new object with speficied prototype, performs all neccessary initializations, and returns the newly created instance.

    function builder(){
      var instance = Object.create(builder.prototype);
      // initialize the instance...
      return instance;
    }
    builder.prototype = {
      // ...
    };
    var object = builder();

The code above will pass an instanceof check as expected. Any object having builder.prototype on its prototype chain will resolve true for object instanceof builder. See on the ECMAScript 5.1 specification:

The big concern now is how to implement the inheritance chain, where objects created through builders inherit from other objects created through builders. Each derived object has to perform ancestor's initializing routines too so is needed a mechanism with prototypes inheriting from ancestor prototypes while builders internally call the ancestor builders. The key is to ensure that the oldest ancestor on the chain will create the new object specifing the child's prototype. There are many solutions, but the simpler is execute builders within the context of the desired prototypes.

  function Grandpa(){
    var instance = Object.create(this);
    // initialize the instance...
    return instance;
  }
  Grandpa.prototype = {
    // ...
  };

  function Parent(){
    var instance = Grandpa.call(this);
    // initialize the instance...
    return instance;
  }
  Parent.prototype = Object.create( Grandpa.prototype );
  // Parent.prototype.x = ...
  // ...

  function Child(){
    var instance = Parent.call(this);
    // initialize the instance...
    return instance;
  }
  Child.prototype = Object.create( Parent.prototype );
  // Child.prototype.x = ...
  // ...

  var grandpa = Grandpa.call( Grandpa.prototype );
  var parent = Parent.call( Parent.prototype );
  var child = Child.call( Child.prototype );

As seen above, now both the declaration and the creation of new objects becomes unnecesarily verbose. That's reason enough to use a helper function. In fact two functions are needed, one to extend prototypes and another to wrap builders to ensure they are executed within a proper context.

function extend( prototype, extension ){
  var object = Object.create( prototype );
  for( var property in extension ){
    if( extension.hasOwnProperty(property) ){
      object[property] = extension[property];
    }
  }
  return object;
};

function builder( builder, prototype, extension ){
  if( extension ){
    prototype = extend( prototype, extension );
  }
  function builderWrap(){
    if( prototype.isPrototypeOf(this) ){
      var context = this;
    }
    return builder.apply( context | prototype, arguments );
  };
  builderWrap.prototype = prototype;
  return builderWrap;
};

The extend function creates a new object with the specified prototype and defines on it as many properties as the own enumerable properties that the extension object has.

The builder function creates a function that will apply to builder the proper context, being the current context (this) if it's an object inheriting from prototype or prototype elsecase. Additionally, any object who has prototype on its prototype chain will pass an instanceof check against the returned function. Optionaly provides acces to extend functionalities: If extension is given, use as prototype the result of extending prototype with extension.

With the help of this tools, the previous example can be rewrited as follows:

  var Grandpa = builder(function(){
    var instance = Object.create(this);
    // initialize the instance...
    return instance;
  }, {
    // Grandpa.prototype...
  });

  var Parent = builder(function(){
    var instance = Grandpa.call(this);
    // initialize the instance...
    return instance;
  }, Grandpa.prototype, {
    // Parent.prototype ...
  });

  var Child = builder(function(){
    var instance = Parent.call(this);
    // initialize the instance...
    return instance;
  }, Parent.prototype, {
    // Child.prototype ...
  });

  var grandpa = Grandpa();
  var parent = Parent();
  var child = Child();

That's all, the target is accomplished. There is no need of classes and no need to use the new keyword. This pattern decouples the creation and initialization of objects from its representation while maintains a true prototypal inheritance pattern. The mechanism to check instance types is the instanceof operator.

When initialization is not needed, there is also a mechanism to inherit one object from another:

  var Grandpa = {
    // ...
  };

  var Parent = extend( Grandpa, {
    // ...
  });

  var Child = extend( Parent, {
    // ...
  });

  var grandpa = Object.create(Grandpa);
  var parent = Object.create(Parent);
  var child = Object.create(Child);