Why did JavaScript adopt prototype-based OOP

Pseudoclassical inheritance in JavaScript

Pseudoclasses in JavaScript

Object-oriented programming in JavaScript works differently than in other popular programming languages. JavaScript does not know any classes, but changeable objects and prototypical delegation.

JavaScript also knows constructors. These are functions that are called using and create instances. They inherit prototypically from an object that is stored in the constructor's property. This is viewed by some as an inconsistency as it stands outside the functional and prototypical alignment of JavaScript. Using functions as constructors is borrowed from class-based languages ​​- JavaScript is not. Douglas Crockford describes this hybrid technology in his book JavaScript. The Good Parts:

JavaScript is conflicted about its prototypal nature. Its prototype mechanism is obscured by some complicated syntactic business that looks vaguely classical. Instead of having objects inherit directly from other objects, an unnecessary level of indirection is inserted such that objects are produced by constructor functions.

This combination of constructor, prototype and instances becomes Pseudo class called because it gives the appearance of a class declaration that instances are created of. An example:

function cat () {} cat.prototype.miau = function () {alert ('meow!'); }; var maunzi = new cat (); maunzi.miau ();

Douglas Crockford describes this ability as helpful, but misleading for those new to JavaScript:

The pseudoclassical form can provide comfort to programmers who are unfamiliar with JavaScript, but it also hides the true nature of the language.

What his criticism ultimately aims at is not the mere declaration of pseudo-classes and the creation of instances. He is concerned that class-based language developers are used to creating complex inheritance hierarchies between classes:

The classically inspired notation can induce programmers to compose hierarchies that are unnecessarily deep and complicated.

Since JavaScript is not a statically typed language, but rather all objects can be changed, rigid inheritance hierarchies in JavaScript make no sense.

Direct inheritance, as Crockford has in mind as an alternative, can be achieved with Object.create () from ECMAScript 5, for example.

Inheritance between pseudo-classes

I have deliberately put the criticism of pseudoclassical inheritance first, because the following is about how it can be implemented in practice. Libraries like Prototype, Mootools, Dojo and Yahoo YUI offer powerful helpers for creating and deriving "classes". Discussing these and explaining how they work would be a bigger undertaking. (I have done this with Yahoo YUI so far.) That is not what this is about, but a possible simpler implementation.

Under the hood, all inheritance in JavaScript works as a prototype. This means that an object delegates requests for non-existent properties to another object that stands behind it, so to speak, and helps it out - see Understanding prototypes.

In the initial situation there are two pseudo-classes, one of which is to inherit from the other:

function Auto () {} Auto.prototype.fahr = function () {alert ('hum, hum'); }; function Elektroauto () {} Elektroauto.prototype.lade = function () {alert ('Loading'); };

If one is generated, it should be able to drive like any other.

Simple, common implementation

A common implementation creates an instance of and uses it as a prototype:

Elektroauto.prototype = new Auto ();

This is pretty good from the idea: A new object that has a prototype is used as a prototype. However, it usually doesn't make sense to call the constructor and create an instance to do this. It's not a specific one that should serve as a prototype. This concept does not apply, especially if the constructor still accepts parameters to initialize the instance.

Instead, I would like to introduce an implementation where the constructor is not called for the purpose of inheritance between the pseudo-classes.

More robust implementation

This path includes the following steps:

  1. Create a new, empty object, which has as a prototype. That is, the prototype reference - the internal property - points to.
  2. Use this object as a prototype for all electric cars. That is, save it in.
  3. Fill the same object with the special properties and methods of s.
  4. Call the super-class constructor in the constructor; This is .

Now let's look at the concrete implementation of the individual steps.

Step 1: Derive the sub-prototype from the super-prototype

For the creation of a new object, which has a given object as a prototype, there is the said method in ECMAScript 5. Until all browsers support this, you can help yourself with the following helper function:

Object_create = Object.create || function (o) {var F = function () {}; F.prototype = o; return new F (); };

It is refrained from creating in the browsers that do not yet know the method. Because the original method from ECMAScript 5 can do a lot more (keyword property descriptors). It should not give the impression that this little helper function is on par and can completely close the browser gap.

In our case, the application looks like this: creates an object that delegates requests for unknown properties to.

Step 2: assign sub-prototype and correct reference

The object just created is now saved in the property of the constructor:

Elektroauto.prototype = Object_create (Auto.prototype);

This makes the object the prototype of all instances that will be created in the future.

An object is initially stored in the property of each function, which has a property. This refers back to the function in example. If the existing object is overwritten, as in the code above, this property is lost. In order for the property to point to and not to in instances, the reference will correct:

Elektroauto.prototype.constructor = Elektroauto;

Step 3: add properties and methods

We can now append properties and methods of to the sub-prototype that has just been created. For example a method:

Elektroauto.prototype.lade = function () {alert ('Loading'); };

Step 4: call the super constructor

In the constructor, it is useful to call the super constructor so that it can take over general initialization. From a technical point of view, the most efficient way to do this is to call the constructor function directly in the context of the instance. The functional implementation is difficult to understand for newbies: You use the methods or, which are common to all functional objects.

function Elektroauto () {// General initialization (Auto) Auto.call (this); // Specific initialization (electric car) // ...}

calls the constructor as a normal function. However, the instance is passed as a context object (the first parameter of). The keyword thus points to the instance in the constructor. Parameters can also be passed on with the help of; they are noted as additional parameters.

The following example calls the super constructor and passes the parameter:

// constructors function Auto (name) {this.name = name; } function electric car (name) {Auto.call (this, name); } // Pseudo-class inheritance Elektroauto.prototype = Object_create (Auto.prototype); Elektroauto.prototype.constructor = Elektroauto; // Create example instances var auto = new Auto ("2CV"); var electric car = new electric car ("La Jamais Contente");

General helper function

The above steps show the concrete application. In order to generalize the procedure, they can be outsourced to a general helper function. It assumes that has already been defined:

function inheritPseudoClass (Super, Sub) {Sub.prototype = Object_create (Super.prototype); Sub.prototype.constructor = Sub; }

The fourth step, calling the super constructor, unfortunately cannot be generalized and shortened easily. Calling directly in the sub-constructor is basically the best solution. A reference to the super constructor could be added to the sub-constructor, e.g.. So you don't have to call it by name in the sub-constructor, instead you would have to make a note, for example. I don't see any advantage in that. Creating a reference to the instance would also be possible, albeit one that is worthy of criticism. That brings us to the complexity of demanding solutions. Therefore, the helper function above does not contain a general solution.

The function expects two pseudo-classes (constructors) as parameters; first the upper class, second the derived, inheriting class. This means that the / example can be implemented as follows:

// constructors function Auto (name) {this.name = name; alert ('car is being built'); } // Put methods on Auto.prototype.fahr = function () {alert (this.name + 'makes hum, hum'); }; function Elektroauto (name) {Auto.call (this, name); alert ('battery and electric motor will be installed'); } // Pseudo-class inheritance inheritPseudoClass (car, electric car); // Add methods to Elektroauto.prototype.lade = function () {alert (this.name + 'is being loaded'); };

To test the inheritance, two instances are created and then both inherited and own methods are called:

// Create example instances var auto = new Auto ('2CV'); // Auto 2CV is being built var elektroauto = new Elektroauto ('La Jamais Content'); // Auto La Jamais content is built, // La Jamais content: battery and electric motor are installed auto.fahr (); // 2CV makes hum, hum electric car.fahr (); // La Jamais content makes hum, hum elektroauto.lade (); // La Jamais content is loading

Limitations and Outlook

In contrast to more complex pseudo-classes, the above do not allow derived methods. Access to a method of the same name in the super class is not that easy. If one were to overwrite the method, it would have to call the super method, which is cumbersome. At most you could create a shortcut called so that you could write.

More complex pseudo-classes like those in Prototype and Mootools allow a simplified call of the overwritten super-methods, including super-constructors. However, this is associated with greater effort. As a rule, overriding methods are packed into an additional function, see for example John Resig's Simple JavaScript Inheritance. A temporary property or (Mootools) is created in this wrapper function, which points to the overwritten function. In Prototype, the wrapper function passes the overridden method as the first parameter to the overwriting method. To do this, it has to laboriously convert the function object into a string and parse the parameter list.

The CoffeeScript meta-language solves this task by automatically rewriting super calls so that they refer to the method of the same name in the super prototype (for example, where points to). I like this approach best, because the wrapping complexity with which Prototype and Mootools buy this "syntactic sugar" is, in my opinion, disproportionate to the benefit. The JavaScript code generated by CoffeeScript is sprawling and completely without sugar, but it is extremely precise and high-performance.

In class-based OOP there is usually something like private Properties and Methods. This can only be solved functionally in JavaScript with closures, see private objects instead of private properties. If local variables or functions are noted in the constructor, these are only accessible to those "public" methods that are created in the same constructor. It is therefore not possible to share private objects between constructors.

Conclusion

The method presented is not new, but is widely used in one way or another. For example, Yahoo UI and the CoffeeScript pseudo-classes work this way.

In summary, we can remember: Pseudo-classes in JavaScript are functions that are used as constructors. Inheritance between pseudo-classes means that the prototype of the sub-class (in the example) has the prototype of the super-class (in the example) as a prototype. Inheritance always means that simple objects delegate to other simple objects via a prototype chain. The method with which such a prototypical delegation can easily be set up is therefore the heart of JavaScript - pseudoclassical inheritance cannot do without this technique either.

The following diagram illustrates the prototype chain of the object. Both pseudo-class prototypes appear in it, which reference each other as prototypes. At the end, as always, is the top prototype,.

electric car

[[Prototype]]Electric car.prototype
Surname'La Jamais Content'

Electric car.prototype

[[Prototype]]Auto.prototype
constructorElectric car
loadingfunction () {…}

Auto.prototype

[[Prototype]]Object.prototype
constructorautomobile
drivefunction () {…}

Object.prototype

[[Prototype]]zero
constructorObject
toString[native function]
toLocaleString[native function]
valueOf[native function]
hasOwnProperty[native function]
isPrototypeOf[native function]
propertyIsEnumerable[native function]

Thanks to the prototypical delegation, all properties of the objects shown in the diagram are available for the instance.