3

Can anyone provide a specific example showing Javascript's prototypal inheritance that demonstrates when it's beneficial to use over a traditional class-based (classical) model?

Other questions I have seen (such as Classical Vs prototypal inheritance, Why was JavaScript implemented using prototypal inheritance?, prototype based vs. class based inheritance) only give a high level discussion, not a specific example (preferably one that you have used in production code).

Community
  • 1
  • 1
Casebash
  • 114,675
  • 90
  • 247
  • 350

5 Answers5

2

There are many reasons why prototypic inheritance is better than classical inheritance:

  1. Prototypic inheritance can be used to simulate classical inheritance. It's a superset of classical inheritance. Vice versa is not possible. This is because in classical inheritance, classes may only inherit from other classes. However, in prototypic inheritance any object can inherit from any other object (and in JavaScript everything is an object).
  2. In JavaScript, every object has an internal proto property that points to its prototype object (the Object object has a prototype null). Since JavaScript is dynamic you may make changes to a prototype object, and these changes will be reflected on every object whose internal proto property points to that prototype (even after the object is created). Thus it can be used to extend the functionality of a group of objects.

There are many more reasons. I'll keep updating it as and when I can recall.

Here's some Java code showing classical inheritance:

public class Employee {
    public String name;
    public String dept;
    public Employee () {
        this("", "general");
    }
    public Employee (String name) {
        this(name, "general");
    }
    public Employee (String name, String dept) {
        this.name = name;
        this.dept = dept;
    }
}

public class WorkerBee extends Employee {
    public String[] projects;
    public WorkerBee () {
        this(new String[0]);
    }
    public WorkerBee (String[] projs) {
        projects = projs;
    }
}

public class Engineer extends WorkerBee {
    public String machine;
    public Engineer () {
        dept = "engineering";
        machine = "";
    }
    public Engineer (String mach) {
        dept = "engineering";
        machine = mach;
    }
}

Here's the equivalent JavaScript code:

function Employee (name, dept) {
    this.name = name || "";
    this.dept = dept || "general";
}

function WorkerBee (projs) {
    this.projects = projs || [];
}
WorkerBee.prototype = new Employee;

function Engineer (mach) {
    this.dept = "engineering";
    this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Interesting, but still pretty much equivalent – Casebash Oct 11 '11 at 03:01
  • I like your in-depth answer. Just a couple comments: your JS sample doesn't call any super constructor, so a `WorkerBee` won't have a name or a department; also, the internal `proto` or `__proto__` properties are non-standard, so it's best to use them only for debugging purposes, but that doesn't change what you were trying to say. :) – Peter Oct 11 '11 at 06:38
  • Also, 'prototypic'... perhaps you mean 'prototypal'. – Peter Oct 11 '11 at 06:41
  • 1
    All objects do have a [[proto]] property regardless of implementation. The non-standard part is being able to change the property after object creation. –  Oct 15 '11 at 00:10
2

The dynamism and flexibility I hope you can see in the below examples is what gives javascript beneficial advantages over static class based model.

In this example, used on a production page, jQuery wouldn't animate at all in ltIE8 browsers. Since it is only in one specific page where this happens, it wouldn't have made sense to hack the jQuery core which is also loaded on other pages (not to mention it would have to be hosted and not loaded from google). Instead I did ltIE8 conditional script block that modifies the cur method of fx prototype inline which fixes the problem where it would return NaN value for animation steps:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<!--[if lt IE 8]>
<script type="text/javascript">
jQuery.fx.prototype.cur = function() {
var parsed, r;
    if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
        return this.elem[ this.prop ];
    }
r = this.elem.style[this.prop] || jQuery.css( this.elem, this.prop );
return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
};
</script>
<![endif]-->

Another example is an implementation of a library I made for emulating Classical inheritance. The specific implementation in the example is not used on a production page, but all the "classes" I create on production pages are made this way. The significant advantage of this is that you can add and modify functionality (such as inline aliasing shown in this example) because it's not hard coded in the language.

var Person = function Person( name, age ){ //Declare the constructor
this.name = name || "John";
this.age = age || "20";

this.instanceId = "person"+this.constructor._getId(); //Create unique instance id
this.constructor._addInstance( this ); //Make this instance accessible from Person
}

.Inherits( Animal, Monkey )

.Class({

     "static _instances": {},

     "static _curId": 0,

     "static _getId": function(){
     return this._curId++; //In static methods "this" refers to the Person constructor, not to an instance.
     },

     "static _addInstance": function( instance ) {
     this._instances[instance.instanceId] = instance;
     },

     "static alias byId": "getInstanceById", //Inline alias for Person.getInstanceById === Person.byId

     "static getInstanceById": function( id ){
     return ( id in this._instances ) && this._instances[id];
     },

     "alias greet": "sayHello", //alias for the instance method

     "alias sayHi": "sayHello",

     sayHello: function(){
     return "hello from "+this.name;
     },

     eat: function(){
     return this.__super__( "eat", "pizza" ); //Super method call, not really useful in this particular implementation
     }
})

.Implements( whatever ); //emulating interfaces, whatever should be an object that describes how the methods must be implemented


//Instantiating and such works like regular js
var mike = new Person( "mike" );

mike.greet(); //"hello from mike"
mike.sayHi(); //"hello from mike"
mike.sayHello(); //"hello from mike"

mike === Person.byId( "person0" ); //true

There is no implementation for making _underscore prefixed methods actually inaccessible from outside because the overhead wouldn't be worth it on a js-heavy page. Multiple inheritance and super methods only work for the last generation.

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • It is nice how you can construct traditional inheritance, but modify how it work. The first example can actually be done in languages like Pytho: http://ideone.com/ipPIU – Casebash Oct 14 '11 at 22:27
2

I have a few things I would say are distinct advantages. They are all represent significant security weaknesses compared to a strongly typed class-based language but they provide a lot of power to a skilled user.

Subclassing instantiated objects

You can create an adhoc "subclass" of any already instantiated object even without declaring a class:

// Magic code
function child(src) {
    function Child() {};
    Child.prototype = src;
    return new Child;
}

// Base object
var default_options = {
    color: 'red',
    size:  'large',
    font:  'arial'
};

// Child object
var my_options = child(default_options);
my_options.size = 'small';
my_options.font = 'verdana';

my_options.color == 'red';
default_options.font == 'arial';

On browsers that support __proto__ this can be even easier:

var my_options = {
    size: 'small',
    font: 'verdana'
};

// When applying options:
my_options.__proto__ = default_options;
my_options.color == 'red';

This means you can also pass simple objects and then enrich them by attaching them to full class prototypes:

my_options.__proto__ = OptionsProcessor.prototype;

Extending existing objects

Of course the real reason why JavaScript inheritance is so great is that you're going to be dealing with an environment that has already been pretty well established with thousands of objects you may want to enhance. Say you want to use element.querySelectorAll on an older browser. With classical inheritance you're out of luck but with JavaScript inheritance, it is a simple matter of:

(HTMLElement || Object).prototype.querySelectorAll = function(selector) { ... }

This kind of polyfill has a large advantage over something like jQuery because you can use standard code throughout your application and only import the JavaScript when we need it.

Rewiring functions

Say we want to know every time that querySelectorAll is used in case want to replace it with a faster function for simpler queries. We could hijack the function and output to the console every time it is called:

var oldFunction = HTMLElement.prototype.querySelectorAll;
HTMLElement.prototype.querySelectorAll = function(selector) {
    console.log(selector);
    oldFunction.prototype.apply(this, arguments);
};

Applying methods to other classes

JavaScript has a lot of array-like functions. arguments is not an array. Neither is document.getElementsByTagName('div'). This means if we want the first 5 items from the array, we can't use list.slice(0, 5). You could however, apply Array.prototype.slice to the list object:

var divs = document.getElementsByTagName('div');
var first5divs = Array.prototype.slice.call(divs, 0, 5);
Brian Nickel
  • 26,890
  • 5
  • 80
  • 110
  • Only the first example really is about prototypical inheritance though. All the other examples would also work the same on a dynamic language with classical OO like Python. – hugomg Oct 17 '11 at 15:16
  • I would argue that makes Python a classical OO language with prototypical functionality. – Brian Nickel Oct 17 '11 at 16:54
  • No. The monkey patching just makes Python *dynamic* (in the sense that anything can be changed at runtime). From a Classical vs Prototypical sense Python is still firmly Classical. – hugomg Oct 17 '11 at 17:07
  • Monkey patching isn't what makes Python dynamic. There are plenty of classical dynamic languages that don't allow it. The fact is that the ability to change a classish structure at run time is an inherent feature of prototypical languages in direct opposition to classical programming. Classical programming is in the sense "all objects fall into classes and this class of objects does this". Changing what an established class of objects does blends paradigms. – Brian Nickel Oct 17 '11 at 17:52
  • Even Smalltalk allows classses to be modified at runtime. I think that saying it has a prototypal style is kind of pushing it when the original prototypal languages were created precisely to be different from it. – hugomg Oct 17 '11 at 18:23
  • I don't think we're going to reach an agreement on this one. I feel that the ability to and the emphasis on extending and manipulating base objects is one of the defining features of a prototypical language. The fact that it exists in dynamic classical languages speaks to its usefulness and not its conformance to the classical model. If someone said said they wanted to modify Object.toString in C#, I would say that's a terrible idea. If they wanted to do it in JavaScript, I would say why not? That's my prototype litmus test. – Brian Nickel Oct 17 '11 at 20:56
1

In my opinion prototypical is much more flexible.

  1. I can make only one instance of my class Bar inherit from Foo instead of all instances of Bar.

  2. I can decide I don't want Bar to inherit from Foo anymore by setting my Bar.prototype to null or some other object value.

  3. I can at any point decide I want Bar to inherit from Array instead of Foo.

Classical languages do have their advantages however. Like better encapsulation. With prototypical you have to do a lot of closure magic to get properties of your object to "act" as private properties.

Keith.Abramo
  • 6,952
  • 2
  • 32
  • 46
1

I think the biggest reason that we don't see more of the prototypal pattern directly is that the default syntax in Javascript is that pseudo-classical aberration instead of the more favourable Object.create. If you want to really see the prototypal patter shining search for places where this function is used. The following example comes from the Dojo toolkit:


Caveat: I kind of changed my mind about how good this kind of code is since back when I originally wrote this answer. While the basic idea still stands, you should be careful it you have methods that mutate instance ("this") properties. This is because if you invoke a method in the delegate object via the delegator then you might end up setting variables in the delegator instead of in the delegate and that might break some invariants if someone else ends up accessing the delegate directly latter on.

The whole idea is 100% fine you you have immutable objects though.


Dojo defines a general store interface (with methods like get(), add(), etc) that can be used, for example, to abstract a REST API from the server. We would like to create a Cache function that receives any datastore and returns a new version that caches any calls to the get() method (this allows us to decouple the caching from the store-specific behaviour of implementing the actual get())

A first idea would involve using the fact the Javascript is highly dinamic to replace the get method:

//not the actual implementation. Things get more complicated w/ async code.
var oldGet = store.get;
store.get = function(id){
    if(!cache[id]){ cache[id] = oldGet(id); }
    return cache[id];
}

However, this clobbers the original object so you can't access the original method anymore and also makes it trickier to add other modifications in parallel.

A second idea would be to make a more robust solution using delegation:

function Cache(obj){
    this.obj = obj;
}
Cache.prototype = {
    get: function(){
        //do things involving this.obj...
    }
};

This looks promising, until you remember that the resulting Cache object needs to implement the store interface. We could try adding all the methods by hand:

Cache.prototype = {
    //...
    put: function(){ return this.obj.apply(this, arguments); },
    //...
}

but not only would that be cumbersome and error prone (its so easy to forget something) it would not even be as powerful as the object-modifying solution since we lose access to methods in that original object that aren´t from the store interface.

Well, the way to do this kind of "automatic delegation" is inheritance, but it would initialy seem useless in this case since you would need to create a new cache subclass for every possible store class or you would need some sort of fancy multiple-inheritance mixin. Enter prototypal inheritance to save the day. We can easily make a new obejct that adds functionality to an old one without modifying it or having to fiddle with the class hieharchies

dojo.store.Cache = function(masterStore, cachingStore, options){
    //...
    return dojo.delegate(masterStore, {
        //... 
        get: function(id, directives){
            //...
        }
        //...
    }
}

Where dojo.delegate is a function that creates a new object with all the properties in the second argument and whose prototype will be the first argument.


Non JS theoretical ramblings: Prototypal inheritance can be used even more agressively in even more delegation scenarios in a language like Self that allows multiple prototypes and also direct access and modification of prototypes at runtime. For example, it is possible to implement the State pattern from the GoF by delegating all suitable methods to a prototype and changing the prototype whenever the state changes.

hugomg
  • 68,213
  • 24
  • 160
  • 246