EcmaScript's perspective on the general theory of OOP

Hello, Habr!



To this day, I have only been engaged in translations of articles, interesting, in my opinion, by English-speaking authors. And now it's time to write something yourself. For the first article, I chose a topic that, I am sure, will be useful to junior developers who want to grow to the "middle", because it will analyze the similarity / difference between JavaScript and classical programming languages ​​(C ++, C #, Java) in terms of OOP. So, let's begin!



General Paradigm Provisions



If we look at the definition of JavaScript on Wikipedia , we will see the following concept:

JavaScript (/ ˈdʒɑːvɑːˌskrɪpt /; abbr. JS /ˈdʒeɪ.ɛs./) is a multi-paradigm programming language. Supports object-oriented, imperative and functional styles. It is an implementation of the ECMAScript language (ECMA-262 standard).


As follows from this definition, JavaScript does not exist by itself, but is an implementation of some EcmaScript specification. In addition to it, other languages ​​implement this specification.



The following paradigms are present in EcmaScript (hereinafter ES):





OOP in ES is implemented at a prototype organization . From novice developers in response to the question: "How is OOP in JS different from OOP in classical languages." As a rule, they get very vague: “Classes are in classical languages, and prototypes in JS”.



In reality, the situation is a bit more complicated. In terms of behavior, the difference between the Dynamic Class Organization and the Prototype Organization is small (it certainly exists, but not so global).



Take a look at Python or Ruby. In these languages, OOP is based on a dynamic class organization. In both of these languages, we can dynamically change the class of an object as the program progresses and changes within the class also dynamically affect the entities it generates. Just like in JS, but in JS, OOP is based on prototypes.



A significant difference between languages ​​with a Static class organization and a Prototype organization. The difference in itself is “there are classes. here prototypes ”are not so significant.



What is Static Class Organization based on?



The basis of this type of OOP are the concepts of “Class” and “Essence”. A class is a certain formalized generalized set of characteristics of entities that it can generate. Those. This is a certain general plan of all objects generated by it.



Characteristics are of two types. Properties (description of an entity) and methods (entity activities, their behavior).



Entities generated by a class are copies of this class, but with initialized properties. As we see, the class strictly regulates the description of an entity (providing a strictly defined set of properties) and its behavior (providing a strictly defined list of methods).



Here is a small example on JAVA:



class Person{ String name; //  int age; //  void displayInfo(){ System.out.printf("Name: %s \tAge: %d\n", name, age); } }
      
      





Now create an instance of the class:



 public class Program{ public static void main(String[] args) { Person tom; } }
      
      





Our tom entity has all the characteristics of the Person class, it also has all the methods of its class.



The OOP paradigm provides a very wide palette of features for reusing code, one of these features is Inheritance .



One class can extend another class, thereby creating a generalization-specialization relationship. In this case, the properties of the general class (superclass) are copied to the essence of the descendant class when they are created, and the methods are available by reference (according to the hierarchical chain of inheritance). In the case of static class typing, this chain is static , and in the case of dynamic typing, it can change during program execution. This is the most important difference. I advise you now to remember this moment. Further, when we get to the Prototype organization, the essence of the problem of the answer “there are classes, there are prototypes” will become obvious.



What are the disadvantages of this approach?



I think it’s obvious that:





What is the prototype organization based on?



A key concept of the prototype organization is a dynamic mutable object (dmo). DMO does not need a class. He himself can store all his properties and methods.



When setting a DMO of a property, it checks for the presence of this property in it. If there is a property, then it is simply assigned; if it is not, then the property is added and initialized with the passed value. DMOs can change their signature during the program as many times as they like.



Here is an example:



 //        const Person = { name: null, age: null, sayHi() { return `Hi! My name is ${this.name}. I'm ${this.age} years old.` } } const Tom = { //-       } Tom.__proto__ = Person;
      
      





I think everyone in the subject knows that class syntax has appeared in ES6, but this is nothing more than syntactic sugar, i.e. prototypes under the hood. The code above should not be taken as good coding practice. This is nothing more than an illustration, it is presented in this form (now all normal people use ES6 classes) so as not to confuse the reader and emphasize the difference in theoretical concepts.



If we output the Tom object to the console, we will see that the object itself has only the _proto_ link, which is always present in it by default. The reference points to the Person object, which is a prototype of the Tom object.



A prototype is an object that serves as a prototype for other objects or an object in which another object can draw properties and methods if they are needed.



The prototype for an object can be any object, moreover, an object can reassign its prototype during the program.



Let's get back to our Tom:



 Tom.name = 'Tom'; //    Tom.surname = 'Williams'; //    Tom.age = 28;//    Tom.sayHi();//  sayHi,      ,    ,       const tomSon = { name: 'John', age: 5, sayHi() { return this.__proto__.sayHi.call(this) + `My father is ${this.__proto__.name} ${this.surname}`; } } //,     tomSon.__proto__ = Tom; tomSon.sayHi();//  "Hi! My name is John. I'm 5 years old.My father is Tom Williams"
      
      





Note that the name, age properties and sayHi method are the properties of the tomSon object. At the same time, we in tomSon sayHi explicitly call the sayHi prototype method as if it were in the Tom object, but in fact it is not there, and it returns implicitly from the Person prototype. We also explicitly operate on the prototype property name and implicitly get the surname property, which we call as our own property of the tomSon object, but actually it is not there. The surname property is implicitly pulled through the __proto__ link from the prototype.



We continue the development of the history of our Tom and his son John.



 // ,    (  ) //  ,   ,    , //      const Ben = { name: 'Ben', surname: 'Silver', age: 42, sayHi() { return `Hello! I'm ${this.name} ${this.surname}. `; } } tomSon.nativeFather = Tom; tomSon.__proto__= Ben; tomSon.sayHi(); //    (),     () //   'Hello! I'm John Silver. My father is Ben Silver'
      
      





Please note that during the program we changed the prototype of the already created object. This is the similarity of the Prototype Organization and the Dynamic Class Organization . That is why the answer "there are classes, there are prototypes" to the question "what is the difference between classical languages ​​and JavaScript?" not quite correct and indicates some misunderstanding of the theory of OOP and its implementation on classes and / or prototypes.



With the Prototype organization, unlike the Static class one, we have the opportunity to make changes to the prototype after creating an entity that inherits the properties from this prototype, and these changes will affect the already created entity.



 Ben.hobbies = ['chess', 'badminton']; // tomSon   ,             ,      tomSon.sayAboutFathersHobies = function () { const reducer = (accumulator, current) => {`${accumulator} and ${current}`} return `My Father play ${this.hobbies.reduce(reducer)}` } tomSon.sayAboutFathersHobies(); //  'My Father play chess and badminton'
      
      





This is called the prototype organization delegating model or prototype inheritance .



How is the ability of an entity to implement certain behavior determined?



In a static class organization, this operation involves checking the entity for membership in a particular class that implements the required behavior. In the prototype organization, there is the concept of duck typing . In the case of duck typing, checking the entity for the ability to implement a specific behavior will mean directly testing the entity for the ability to implement this behavior at a particular point in time, i.e. in different parts of the program, the result of the check may be diametrically opposite.



What are the advantages of a prototype approach?





What are the downsides?





Conclusion



On this we will end today. I hope that I was able to convey the idea that the difference between classical languages ​​and JavaScript is not related to the presence / absence of classes and the presence / absence of prototypes, but rather to the static / dynamic nature of the organization.



Of course, much has not been considered. I would not want to write articles that are too long, so we will discuss the features of the Cascade Model in the prototype organization and OOP tools (Polymorphism, Encapsulation, Abstraction, etc.) in subsequent articles.



All Articles