📜 ⬆️ ⬇️

Does JavaScript need classes?

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!"; } } ///  class TalkingDuck extends Duck{ constructor(name){ super(name); }, quack(){ return super.quack() + " My name is " + this.name; } } ///  var donald = new TalkingDuck("Donald"); 

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!"; }; ///  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; }; ///  var donald = new TalkingDuck("Donald"); 

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:
  1. 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.
  2. 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$ = {// "$"      "": duck$ == Duck.prototype name: "", quack: function(){ return this.name +" Duck: Quack-quack!"; } }; var donald = { __proto__: duck$, name: "Donald" }; var daffy = { __proto__: duck$, name: "Daffy" }; console.log( donald.quack() ); // Donald Duck: Quack-quack! console.log( daffy.quack() ); // Daffy Duck: Quack-quack! console.log( duck$.isPrototypeOf(donald) ); // true 

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$ ); // true 


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..."); //  : var value = "   " + this.name; //  ,      //   ,    : Object.defineProperty(this, 'lazy', { value: value, writable: true, enumerable: true }); console.log(" ."); return value; }, //     // ,   - // (       ): set lazy(value){ console.log("  lazy..."); Object.defineProperty(this, 'lazy', { value: value, writable: true, enumerable: true }); } }; console.log( obj.lazy ); //   lazy... //     console.log( obj.lazy );//     //     obj.lazy = "";//   , ..    console.log( obj.lazy ); //  

The advantages of this technique include:


comparison table

PrototypeClass (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 classes
Antero Taivalsaari - Classes vs. prototypes: some philosophical and historical observations [PDF]
Mike Anderson - Advantages of prototype-based OOP over class-based OOP

Source: https://habr.com/ru/post/175029/


All Articles