

JavaScript is considered to be a prototype-oriented programming language. But, oddly enough, almost no one uses this approach: most of the popular JS frameworks explicitly or implicitly operate with classes.
In this article I want to talk about an alternative way of programming in JavaScript, without using classes and constructors - a pure prototype OOP and the features of its implementation in ECMA Script 5.
OOP can be divided into two groups: class-oriented (classical) and prototype-oriented. The classical approach reflects Aristotle’s view of the world in which everything is described by ideal concepts. The prototype OOP is closer to the philosophy of Ludwig Wittgenstein, which does not rely on the strict categorization and classification of everything and everyone, but tries to present the concepts of the subject domain material and intuitive (as far as possible). A typical argument in favor of prototyping is that it is usually much easier to first understand concrete examples, and only then, studying and summarizing them, identify some abstract principles and subsequently apply them.
JavaScript, according to this classification, is somewhere in the middle: on the one hand, it contains prototypes, on the other hand, classes and the new operator, as a means of creating new objects, which is not characteristic of the prototype-oriented approach.
')
Classes
There are no classes in JavaScript, you say. I would not say so.
By classes in JS, I mean constructor functions: functions called when creating an instance (executing the
new
operator), with reference to the
prototype — an object containing properties (data) and methods (functions) of a class.
As is known , in the ECMA Script 6 it is possible to enter the
class
keyword:
class Duck{ constructor(name){ this.name = name; }, quack(){ return this.name +" Duck: Quack-quack!"; } }
But in essence, this innovation will not bring anything substantial (for example,
public
,
private
modifiers). This is nothing but syntactic sugar for a similar construct:
var Duck = function(name){ this.name = name; }; Duck.prototype.quack = function(){ return this.name +" Duck: Quack-quack!"; };
Consequently, the
classes in the current version of JS already exist, only there is no convenient syntax for creating them .
In the end, let's define what a class is. Here is the definition (
from wikipedia ):
A class is a kind of abstract data type in OOP, characterized by the way of its construction. The essence of the difference between classes and other abstract data types is that when defining a data type, a class defines both an interface and an implementation for all its instances, and the call to the constructor method is required.
Following this definition, the constructor function is a class:
Is the constructor function an abstract data type? - Yes.
The constructor function (along with the properties from the prototype) defines both the interface and the implementation? - Yes.
Is invoking constructor required when instantiating? - Yes.
Prototypes
The prototype differs from the class in that:
- This is an object ready to use that does not need to be instantiated. It can have its own state (state). We can say that the prototype is a class and an instance merged into one entity, roughly speaking, by Singleton.
- Calling the constructor when creating an object (cloning a prototype) is optional.
The essence of the prototype OOP itself is very simple. Even easier than the classic. Difficulties in JS arise from the attempt to make it look like the way it is implemented in Java: in Java, new objects are created using the new operator applied to the class. In JS - the same. But, since JS seems to be a prototype language, and there should not be classes in it,
by definition , the concept of a function constructor was introduced. The trouble is that there is no syntax for the normal description of the binding constructor prototype in JavaScript. As a result, we have a
sea ​​of ​​libraries that correct this annoying omission.
In the prototype-based approach, there is no new operator, and the creation of new objects is done by cloning existing ones.Inheritance
So, the essence of prototype (delegating) inheritance is that one object can refer to another, which makes it a prototype. If, when accessing a property / method, it is not found in the object itself, the search will continue in the prototype, and further in the prototype prototype, etc.
var duck$ = {
daffy
and
donald
use one common
quack()
method, which provides them with the duck $ prototype. From the prototype point of view,
donald
and
daffy
are clones of the
duck$
object, and from a class-oriented “instances of the class”
duck$
.
If you add / change some properties directly in the
donald
(or
daffy
)
daffy
, then it can also be considered the “heir of the class”
duck$
.
V8 does just that , creating hidden classes each time a property is added.
Do not forget that the
__proto__
property is not yet
standardized . Officially manipulating the
__proto__
property
__proto__
possible with ECMAScript 5 using
Object.create
and
Object.getPrototypeOf
:
var donald = Object.create(duck$, { name: {value: "Donald"} }); console.log( Object.getPrototypeOf(donald) === duck$ );
Initialization
Unlike the class-oriented approach, the presence of the constructor and its call when creating an object based on the prototype (cloning) is not necessary.
How, then, to initialize the properties of the object?
Simple, non-calculated default values ​​for the properties can be immediately assigned to the prototype:
var proto = { name: "Unnamed" };
And if you need to use the calculated values, then along with ECMA Script 5 we come to the rescue:
Lazy (deferred) initialization
Lazy initialization is a technique that allows you to initialize a property when it is first accessed:
var obj = { name: "", get lazy(){ console.log(" lazy...");
The advantages of this technique include:
- Splitting a constructor into smaller accessor methods “automatically”, as prevention of the appearance of long constructors (see the long method ).
- Increase in performance, because unused properties will not be initialized.
comparison table
Prototype | Class (ECMA Script 5) | Class (ECMA Script 6) |
---|
Description of the data type ("class") |
var duck$ = { name: "Unnamed", get firstWords(){ var value = this.quack(); Object.defineProperty( this, 'firstWords', {value: value} ); return value; }, quack: function(){ return this.name +" Duck: Quack-quack!"; } }; | var Duck = function(name){ this.name = name||"Unnamed"; this.firstWords = this.quack(); }; Duck.prototype.quack = function(){ return this.name +" Duck: Quack-quack!"; }; | class Duck{ constructor(name="Unnamed"){ this.name = name; this.firstWords = this.quack(); }, quack(){ return this.name +" Duck: Quack-quack!"; } } |
Inheritance |
var talkingDuck$ = Object.create(duck$, { quack: {value:function(){ return duck$.quack.call(this) + " My name is " + this.name; }} }); | var TalkingDuck = function(name){ Duck.call(this, name); } TalkingDuck.prototype = Object.create(Duck.prototype); TalkingDuck.prototype.constructor = TalkingDuck; TalkingDuck.prototype.quack = function(){ return Duck.prototype.quack.call(this) + " My name is " + this.name; }; | class TalkingDuck extends Duck{ constructor(name){ super(name); }, quack(){ return super.quack() + " My name is " + this.name; } } |
Creating instance objects and initializing |
var donald = Object.create(talkingDuck$); donald.name = "Donald"; | var donald = new TalkingDuck("Donald"); | var donald = new TalkingDuck("Donald"); |
Part 2 -
Performance: creating classes through __proto__
References:Dr. Axel Rauschmayer -
Myth: JavaScript needs classesAntero Taivalsaari -
Classes vs. prototypes: some philosophical and historical observations [PDF]Mike Anderson -
Advantages of prototype-based OOP over class-based OOP