Demonstrating the benefits of Javascript inheritance in relation to classes

Can someone provide a concrete example showing the prototypal inheritance of Javascript that demonstrates when it is beneficial to use it in a traditional classical (classical) model?

Other questions that I saw (for example, Inheriting the prototype of classic V , Why was JavaScript implemented using prototypal inheritance? Based on prototype and class-based inheritance ) give only a high-level discussion and not a specific example (preferably the one you used in the production code).

+3
javascript inheritance
source share
5 answers

I think the biggest reason we don't see a prototype template anymore is because the default syntax in Javascript is pseudo-classical aberration, not the more favorable Object.create. If you want to really see how the prototype shine is trying to find places where this feature is used. The following example is from the Dojo toolkit:


Caution I changed my mind a bit about how good this code is since I originally wrote this answer. Although the basic idea is still worth it, you have to be careful, you have methods that mutate the properties of the instance ("this"). This is because if you call a method in the delegation object through a delegate, then you can end up setting variables in the deletor instead of the delegate, and this can lead to a violation of some invariants if someone else gets access to the delegate, which is the last one .

The whole idea is 100% good because you have immutable objects.


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

The first idea would include using the fact that Javascript is very dynamic 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 compresses the original object, so you can no longer access the original method, and also make it difficult to add other changes in parallel.

A second idea would be to create 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 must implement the storage interface. We could try adding all the methods manually:

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

but not only it would be cumbersome and error-prone (it’s so easy to forget something), it would not even be as powerful as the solution modifying the object, since we lose access to the methods in this original object, which is not a store .

Well, the way to do this “automatic delegation” is to inherit, but in this case it will seem useless, since you will need to create a new cache subclass for each possible storage class, or you will need some fancy combination of multiple inheritance. Enter prototype inheritance to save the day. We can easily create a new obejct that adds functionality to the old one without changing it, or we don’t have to bother with the hieharchies class

 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 is the first argument.


Theoretical deviations from JS: prototype inheritance can be used even more aggressively in even more delegation scenarios in a language such as Self, which allows the use of several prototypes, as well as direct access and modification of prototypes at runtime. For example, you can implement the State pattern from GoF by delegating all the appropriate methods to the prototype and changing the prototype whenever the state changes.

+1
source share

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

  • Prototype inheritance can be used to model classical inheritance. This is a superset of classical inheritance. Conversely, impossible. This is because in classical inheritance classes can only be inherited from other classes. However, in prototype inheritance, any object can inherit from any other object (and in JavaScript, everything is an object).
  • In JavaScript, every object has an internal proto property that points to its prototype object ( Object has a null prototype). Since JavaScript is dynamic, you can make changes to the prototype object, and these changes will be reflected on each object, the proto internal property points to this prototype (even after creating the object). Thus, it can be used to expand the functionality of a group of objects.

There are many more reasons. I will constantly update it when and when I remember.

Here is some Java code showing classic 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; 
+2
source share

Dynamism and flexibility. Hopefully, you can see in the examples below that javascript offers beneficial advantages over a model based on a static class.

In this example used on the work page, jQuery will not display at all in ltie8 browsers. Since this is only on one specific page where this happens, it would be useless to hack the jQuery Kernel, which also loads on other pages (not to mention that it must be hosted and not downloaded from Google). Instead, I made a conditional script block ltIE8 that modifies the fx inline prototype cur method, which fixes the problem when it returns a NaN value for the 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 the implementation of the library that I created to emulate classical inheritance. The concrete implementation in the example is not used on the work page, but all the "classes" that I create on the production pages are created in this way. The significant advantage of this is that you can add and change functionality (for example, the built-in anti-aliasing shown in this example), because it is not hardcoded 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 pre-launched _underscore discarding methods inaccessible from the outside, the overhead will not cost on the js-heavy page. Only multiple inheritance and super work methods for the latest generation.

+2
source share

I have a few things that I would say are great benefits. All of them represent significant security flaws compared to a strongly typed class-based language, but they provide more power to a skilled user.

Objects of subclasses created for instances

You can subclass adhoc of any object you have already created, even without declaring the 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'; 

In browsers that support __proto__ , this can be even simpler:

 var my_options = { size: 'small', font: 'verdana' }; // When applying options: my_options.__proto__ = default_options; my_options.color == 'red'; 

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

 my_options.__proto__ = OptionsProcessor.prototype; 

Extension of existing facilities

Of course, the real reason why JavaScript inheritance is so great is that you are going to deal with an environment that is already pretty well set up with thousands of objects that you can improve on. Suppose you want to use element.querySelectorAll in an older browser. With classic inheritance, you're out of luck, but with JavaScript inheritance, it's simple:

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

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

Rewind Functions

Let's say we want to know every time querySelectorAll used if you want to replace it with a faster function for simpler queries. We could capture a function and output it to the console every time it was 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 many functions like an array. arguments not an array. Also there is no document.getElementsByTagName('div') . This means that if we want to get the first 5 elements from an array, we cannot use list.slice(0, 5) . However, you can apply Array.prototype.slice to the list object:

 var divs = document.getElementsByTagName('div'); var first5divs = Array.prototype.slice.call(divs, 0, 5); 
+2
source share

In my opinion, the prototype is much more flexible.

  • I can only make one instance of the Bar class, inherited from Foo, and not all instances of Bar.

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

  • I can decide at any time whether I want Bar to inherit from Array instead of Foo.

However, classical languages ​​have their advantages. What is the best encapsulation. With the prototype, you need to do a lot of closing magic so that the properties of your object "act" as private properties.

+1
source share

All Articles