JavaScript revolution. Literally

  • Tutorial
Today is April 22, 2017, the birthday of a person without whom events a hundred years ago would not have taken place. So, there is reason to talk about revolutionary history. But where does Habr? - It turned out that everything in this world can have the most unexpected connection.

Vladimir Ilyich Lenin

As we know, JavaScript can be seen as an object-oriented language in the sense that “it all comes down to objects” (in fact, a prototype of an object). On the other hand, in a broad philosophical question, we are also dealing with objects and prototypes. For example, when considering the object of the Revolution and its prototype described in Capital. So let's just talk about those events in modern language!

With all the revolutionary directness, immediately trumps the table!
This article is essentially a brief explanation of the inheritance mechanism in JavaScript, that is, one of the aspects of OOP. The advanced proletarian developers will find absolutely nothing new in it. However, I hope the material can serve as a memorable, figurative memo for a wide range of interested in developing on JS. In addition, perhaps someone will enhance their knowledge of Russian history

Create two objects:

var stalin = {
   gulag: true,
   mustache: true,
   hero: 1
} 
var lenin = {
   baldHead: true,
   armand: false,
   criticalImperialism: true
}

We indicate that one object is the successor of another through the __proto__ property (this form of writing is available in all browsers except IE10- and is included in ES2015 ). One heir and the other a prototype. We check the properties of the successor object, __proto__ appeared there:

stalin.__proto__ = lenin;
console.log(stalin);

If the property is not detected directly in the object, it is searched in the parent object (prototype). Let's try:

console.log(stalin.baldHead);  //  true

Yes, the property is available, but its value does not suit us. We rewrite it, while the property of the parent object does not change:

stalin.baldHead = false;
console.log(lenin.baldHead);  //  true - свойство в прототипе не поменялось

By the way, what is the prototype of the prototype?

In JS, an object, except for one case (more on that below) inherits from Object .__ proto__ (look in the console). Including standard methods available by default: such as, for example, Object.toString (), Object.valueOf (), and so on.

But how do we list the properties of the object itself, without the properties of its parent, so as not to perform unnecessary operations? - There is hasOwnProperty for this:

for (var key in stalin) {
   if (stalin.hasOwnProperty(key)) console.log(key + ": " + stalin[key])
}

By the way, if the object already has its own property, then after assigning the prototype it will not be overwritten by the value from the prototype, but will remain as it was:

var dzerjinskiy = {
   mustache: true,
   baldHead: false
}
dzerjinskiy.__proto__ = lenin;
console.log(dzerjinskiy.baldHead);  //  false - при присвоении прототипа осталось тем же

Finally, you may need a simple dummy object with no properties, which is only needed to write values. Then we don’t have to check hasOwnProperty when listing its properties:

var zyuganov = Object.create(null);
zyuganov.experience = 25;
console.log(zyuganov.toString);  //  undefined

When checking, it turns out that the empty object does not even have standard methods, such as toString (). By the way, the Object.create method (prototype [, {}]) was used above - a method that allows you to create an object with the required prototype (including null) and properties (optional).

F.prototype and new

You can create a constructor to make it easier to create instances whose parent is a single object:

var marks = {
   marxism: true,
   engels: "friend",
   beard: 80
} 
function Marksist(name) {
   this._name = name;
   this.__proto__ = marks;
} 
var kamenev = new Marksist("kamenev");
console.log(kamenev);

We see that comrade Kamenev also has a beard.

Lev Borisovich Kamenev

But what about the Revolution? You can add a new value and method to the prototype, then the descendant can use this method:

marks.revolution = "future";
marks.deal = function() {return this.revolution};  //  this ссылается на объект marks

A new value appeared in the descendant:

console.log(kamenev.revolution);  //  "future"

We add a property or method to the prototype, and it appears in descendants without the need to redefine them. The power of prototype inheritance!

Naturally, the value in the descendant can be modified, this will not affect the remaining descendants of the prototype:

kamenev.revolution = "now";
console.log(kamenev.deal());  //  "now"

As you can see, the object did not initially have a method, but after adding the method to the prototype, we can call it, moreover, with values ​​modified in the descendant.

For support in all browsers, incl. old, there is another way:

function Marksist(name) {
   this._name = name;			
}
Marksist.prototype = marks;
var kamenev = new Marksist("Каменев");
console.log(kamenev);   //  выведет объект с одним своим свойством и объектом marks в __proto__

Prototype makes sense only in constructors (written in JS with a capital letter), it essentially performs only one action, namely: it indicates where to refer to the __proto__ property when initializing the constructor function.

If, after creating the first object, we want to create a second one by showing it a different prototype, the prototype of the first object will not change:

Marksist.prototype = {};
var chicherin = new Marksist("Чичерин");
console.log(chicherin.marxism);  //  undefined, в свойстве __proto__ будет стандартный прототип Object
console.log(kamenev.marxism);  //  по-прежнему true, в свойстве __proto__ будет объект marks

It can be seen that a new object with an empty prototype does not have inherited properties, like the first object. But everything can be replayed on the fly:

Marksist.prototype = marks;
var zinovev = new Marksist("Зиновьев");		
console.log(zinovev.marksizm);   //   true
console.log(zinovev.deal());   //   future

It should be noted that changing prototypes is considered a very expensive operation, so playing with prototypes on the fly is not recommended!

In the prototype, we can also specify methods that all descendants will use:

var marks = {
   marxism: true,
   engels: "friend",
   beard: 80,
   shout: function(){
      alert("Я есть товарищ " + this._name + "!")
   }
}
function Marksist(name) {
   this._name = name;			
}
Marksist.prototype = marks;
var dzerjinskiy = new Marksist("Дзержинский");
dzerjinskiy.shout();  //  Я есть товарищ Дзержинский!

Here, this is the object for which the function from the prototype is called, in this case Dzerzhinsky.

Felix Edmundovich Dzerzhinsky
Correctly Felix warns us: in JavaScript always have to be vigilant about the fact, which is currently pointing to the keyword this

You can check if an object is a descendant of the constructor using the instanceof operator:

var zemlyachka = function(tov) {
   var res = false;				
   if (tov instanceof Marksist) res = true;
   return res;
}
console.log(zemlyachka(zinovev));   //  true

Here is an opportunist who will have in practice all the same properties and methods as an ordinary Marxist:

var opportunist = {
   name: "Преображенский",
   marxism: true,
   engels: "friend",
   beard: 80,
   shout: function(){
      alert("Я есть товарищ " + this.name + "!")
   }
};		
opportunist.shout();

We can even tell him the same unique property of his own, and determine the rest in his prototype, that is, keep exactly the same structure as the previous objects:

var plehanov = {
   marxism: true,
   engels: "friend",
   beard: 80,
   shout: function(){
      alert("Я есть товарищ " + this._name + "!")
   }
}
function Socialist (name){
   this._name = name;
}
Socialist.prototype = plehanov;
var opportunist = new Socialist("Попутчик");
console.log(opportunist);  
// структура объекта и прототипа идентичная таковой объекта var zinovev = new Marksist, имена свойств те же

However, at the check it will fail:

console.log(zemlyachka(opportunist));  //  false

Rosalia Samoylovna Zemlyachka
Rosalia Samoilovna sees through the object. There is another approach to checking objects -
Duck typing
If it looks like a duck, swims like a duck and quacks like a duck, then this is probably a duck.
If the subject, the object behaves as we need, then we consider him a communist who should be considered, despite its origin


However, proven Communists can sometimes be mistaken. The instanceof operator compares only object and constructor prototypes, so collisions like this are possible:

var troczkiy = new Marksist("Троцкий");
Marksist.prototype = {};
console.log(troczkiy instanceof Marksist);  //  опа, 1940-й год подкрался незаметно!

Well and of course, remember that everything in JS is an object (or rather, in the prototype chain, all objects except special empty ones come to Object.prototype ), so checking both times will return true :

var john_reed = [10];
console.log(john_reed instanceof Array);  //  true
console.log(john_reed instanceof Object);  //  true

Constructor property Constructor

functions (and indeed all functions) have a prototype property , in which constructor is written : it returns a reference to the function that created the prototype instance. It can be easily lost in further transformations, as JS is not required to keep this link. Suppose we decided to find out the political roots of Lev Davydovich:

var marks = {
   marxism: true,
   engels: "friend",
   beard: 80
} 
function Marksist(name) {
   this._name = name;
} 
Marksist.prototype = marks;
var troczkiy = new Marksist("Троцкий");
var Congress = troczkiy.constructor;
var retrospective = new Congress("My life");
console.log(retrospective);  //  чёрти что, конструктор делает явно не то, чего мы от него ожидаем!

Lev Davydovich Trotsky

Obviously, we could not call the same constructor function that would create a new object for us that would be identical to the first (although constructor , in theory, should point to it!). To get the desired result, just save the constructor property in the Marksist prototype:

var marks = {
   marxism: true,
   engels: "friend",
   beard: 80
} 
function Marksist(name) {
   this._name = name; 
} 
Marksist.prototype = marks;
Marksist.prototype.constructor = Marksist;
var troczkiy = new Marksist("Троцкий");
var Congress = troczkiy.constructor;
var retrospective = new Congress("My life");
console.log(retrospective);  //  вот теперь всё как нужно!

Thus, we do not need to know which constructor created the instance, from which we are now creating a new instance. This information is recorded in the instance itself.

Looking at these transformations, the thought may come to redefine the properties and methods of built-in JavaScript prototypes. Speaking in a revolutionary language, moving to a global, global level

The first coat of arms of the USSR 1924

Nothing can stop us from, for example, changing the Object.prototype method:

Object.prototype.toString = function(){
   alert("К стене!")
}

Or even not such an extremist example:

Array.prototype.manifest= function(){
   return "Призрак бродит по Европе - призрак коммунизма";
}

This style of changing classes (here it would be more accurate to say prototypes) is called monkey patching.

There are two dangers. Firstly, by expanding or modifying the built-in prototypes, we make available changes for all objects that are lower down the property inheritance chain (for Object.prototype, these are generally all JS entities). Then, using this method, we risk calling the new property the old name, thereby overwriting it. If within the same prototype chain we can remember the names of the properties, as part of a large project, where, in addition, other scripts can be connected, the contents of the prototypes of basic entities, like a global object, are better not to touch, otherwise the consequences will be unpredictable.

And secondly, redefinition in the course of program execution can lead to unobvious results:

var pss = {
   name: "Полное собрание сочинений",
   author: "Ленин",
   length: 20
}
var Books = function(){};
Books.prototype = pss;
var firstEdition = new Books;
console.log(firstEdition.length);  //  20
var extendLenin = function(year){
   if (!year) var year = new Date().getFullYear();
   if (year > 1925 && year < 1932) pss.length = 30;
   if (year > 1941 && year < 1966) pss.length = 35;
   if (year > 1966) pss.length = 55;
}
extendLenin();
var fourthEdition = new Books;
console.log(fourthEdition.length);  //  ??

The behavior of the properties of objects inheriting from the prototype (the so-called "class instances") may not be the same as we assume.

Functional Inheritance

In addition to the prototype inheritance paradigm, JavaScript also uses functional inheritance. Using one of the heroes of the Revolution as an example, let us see how it is realized.

Let's create a constructor that:

a) Accepts parameters
b) Possesses public methods that are supposed to be used from the outside of the constructor and its derivatives
c) Possesses private methods that are supposed to be used only inside the constructor and its derivatives

Here is a typical communist:

var Kommunist = function(principles) {
   if (!principles) principles = {};
   this._start = 1902;
   if (principles && principles.start) this._start = principles.start;
   // публичный метод, предполагается к использованию извне
   this.response = function() {
      alert("Наше дело правое!")
   }    
   // приватный метод, по соглашению доступен внутри конструктора и его потомков
   this._experience = function() {
      return (this._start);
   }     
   this.principles = (Object.keys(principles).length > 0 ? principles : {fidelity: 100});  
   this._getPrinciples = function() {
      return this.principles
   }
}

Private methods are customary to write, starting with the underscore.

So, we have a constructor with a set of methods, ready to accept and process arguments. Create an instance of the class:

Kliment Efremovich Voroshilov

function Voroshilov(principles) {
   Kommunist.apply(this, arguments);   
   //  расширяем метод конструктора
   var parentExperience = this._experience;
   this._experience = function() {
      return ("Стаж в ВКП(б) с " + parentExperience.apply(this, arguments));      
   }
   // публичные методы, обращаемся к ним извне
   // геттеры
   this.getPrinciples = function() {
      var p = this._getPrinciples();   
      var char = {
         fidelity: p.fidelity,
         persistence: p.persistence || "достаточная!"
      }
      console.log("Верность: " + char.fidelity + ", стойкость: " + char.persistence)
   }
   this.getExperience = function() {   	
      console.log(this._experience());
      alert("Опыт ого-го!");
   }
   // сеттер
   this.setExperience = function() {
      this._experience = function() {
         return ("Стаж в ВКП(б) со Второго съезда");  
      }
   }
}
var ke = {fidelity: 101, persistence: 100, start: 1903}
var voroshilov = new Voroshilov(ke);

Note: the constructor is called relative to this to write all its methods to it, and with the arguments array , which contains all the arguments specified during the call (the ke object ).

Then we can observe how the getter and setter work, as well as other public methods:

voroshilov.getExperience();  //  получили значение
voroshilov.setExperience();  //  заменили метод предустановленным в экземпляре класса
voroshilov.getExperience();  //  получили новое значение
voroshilov.getPrinciples();  //  получили результат выполнения публичного метода с заданными параметрами

For a change, you can call the constructor without parameters.

Class Entity

Finally, with the release of ES6 (ES2015), we have the opportunity to use the class statement directly in JavaScript. In fact, nothing has changed in the prototype inheritance device, but now JS supports syntactic sugar, which will be more familiar to many programmers who come from other languages.

class Marksist {
   constructor(name) {
      this._name = name
   }
   enemy() {
      return "capitalism"
   }				
   static revolution(){
      return "Революция нужна"
   }
}

JS classes have three kinds of methods:

- constructor (executed when the class instance is initialized);
- static (static methods available when the class is called, but not available in instances);
- conventional methods.

Now remember in a constant (ES6 also allows this type of variable) a very important date, and then we define a Menshevik who is the heir to a Marxist:

const cleavage = 1903;
class Menshevik extends Marksist {
   constructor(name) {
      super();
      this._name = name;					
   }
   static revolution() {
      var r = super.revolution();
      return r + ", но потом";
   }
   ["che" + cleavage]() {
      alert("Пароль верный!")
   }
   hurt() {
      alert("Ленин был прав")
   }
}
let chkheidze = new Menshevik("Чхеидзе");

image

There are two innovations here:

- super () in the first case initializes the constructor of the base class, in the second calls the method to which we add a new behavior in the descendant;
- computed method names ( ["che" + cleavage] ), now we do not need to immediately know the method name.

The static method is available when the class is called, but not when the instance is called:

console.log(Menshevik.revolution());   //   работает
console.log(chkheidze.revolution());   //   is not a function, в экземпляре её нет

The result of the following code is already clear:

chkheidze.hurt();   //   вызов метода класса
console.log(chkheidze.enemy());   //   вызов метода базового класса			
chkheidze.che1903();   //   вызов метода с вычисляемым именем

Above we have shown the most basic features of inheritance through classes ( class ) in JavaScript. The conscious proletariat, with due revolutionary perseverance, will find many articles on the network that more fully cover the issue of innovations in ES6.

Also popular now: