Good day. I study ways of organizing inheritance in JavaScript and wrote a small example:

function Foo(name) { this.name = name; } Foo.prototype.myName = function() { return this.name; }; function Bar(name, label) { Foo.call(this, name); this.label = label; } Bar.prototype = Foo.prototype; Bar.prototype.myLabel = function() { return this.label; }; var a = new Bar("a", "obj a"); a.myName(); a.myLabel(); 

A question appeared on the line:

 Bar.prototype = Foo.prototype; 

Trying to understand the difference between

 Bar.prototype = new Foo() 

and

 Bar.prototype = Foo.prototype; 

stumbled upon an article that says

Bar.prototype = Foo.prototype doesn’t create a new object for Bar.prototype to be linked to. It just makes it possible that it will be a reference to Foo.prototype. This is when you’ve changed it.

The question is in the last sentence. Why, when adding the Bar property to the prototype, we automatically change the prototype of the Foo object? If I understood correctly, then just when adding a property or method to the Foo object, the Bar object should also change, since he refers to the Foo prototype. Help to understand please.

    2 answers 2

    Let's start with an abstract example:

     var a = {test: 11} b = a; b.test = 12; console.log(a.test); // Выведет 12! 

    This is because the objects in JS are assigned and passed by reference and not by value.

    The <An Object>.prototype is an object. When you run the code:

     Bar.prototype = Foo.prototype; 

    you assign the Bar.prototype property a reference to the Foo.prototype object. As a result, any change in the Bar.prototype property leads to a change in Foo.prototype , which is stated in the cited quotation:

    This is when you’ve changed it.


    A small lyrical digression .

    Generally speaking, I would recommend that you never use a construct:

     Bar.prototype = new Foo(); 

    and all those who advise you this - feel free to send teach the basics of JS . The whole point is that by calling new Foo() you call the object's constructor. In this case, the constructor itself may, on the one hand, impose restrictions on the passed arguments, and on the other, have side effects. Let us examine each of these cases separately.

    Suppose you have a constructor that imposes restrictions on its arguments:

     Foo = function(a) { if (typeof a === 'undefined') { throw new Error('You have to set the first argument.'); } this.a = a; } 

    In this case, you can no longer just take and run:

     Bar.prototype = new Foo(); 

    because You need to explicitly pass the argument to the constructor, which is completely meaningless at the moment of describing the inheritance hierarchy. The most interesting thing is that the value of the a parameter will still be overwritten when the constructor Foo called in the child constructor Bar . Therefore, the construction of new Foo() is also devoid of meaning.

    Now suppose the parent constructor has side effects:

     Foo = function(a) { console.log('Here I am!'); } 

    Using:

     Bar.prototype = new Foo(); 

    and further:

     var Bar = function() { Foo.call(this); } 

    the string " Here I am! " will be displayed sometime . Agree, this is not always the desired behavior of the system.

    Well, another curious fact: even if at the moment the parent constructor has no side effects or restrictions on the arguments, this does not mean that he will remain so forever . It’s better to do it right away right away than to nervously debug the code in search of an error when everything breaks.


    I will give, for reference, the correct implementation of inheritance in JS:

     // Базовый конструктор var Foo = function() { // ... }; Foo.prototype.doSomething = function() { // ... }; // Дочерний конструктор var Bar = function() { // Вызываем базовый конструктор для текущего объекта. Foo.call(this); // ... }; // Устанавливаем правильное значение в цепочке прототипов. Bar.prototype = Object.create(Foo.prototype, { // Выставляем правильную функцию-конструктор для всех создаваемых // объектов. constructor: { value: Bar, enumerable: false, writable: true, configurable: true } }); // Расширяем прототип дочернего "класса". Этот шаг должен идти // СТРОГО ПОСЛЕ установки значения Bar.prototype. Bar.prototype.doAnotherAction = function() { // ... }; 

    In the case when you cannot use Object.create (old barsers), you can either use one of the existing polyfiles, or make everything handles (via an anonymous constructor):

     var inherits = function(ctor, superCtor) { // Временный конструктор, который не делает ничего и нужен // только для разрыва прямой связи между прототипами ctor // и superCtor. Его использование позволяет менять прототип // дочернего конструктора, не боясь сломать родительский. var Tmp = function() {}; Tmp.prototype = superCtor.prototype; // Обратите внимание, вызов new Tmp() не имеет АБСОЛЮТНО // никаких побочных эффектов и не накладывает ограничений // на передаваемые значения. ctor.prototype = new Tmp(); // Выставляем правильную функцию-конструктор для всех // создаваемых объектов. ctor.prototype.constructor = ctor; }; 

    Given all the above, the universal function of inheritance can be:

     var inherits = (function() { if (typeof Object.create === 'function') { // Используем более простой вариант, если Object.create существует. return function(ctor, superCtor) { ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }; } // Используем временный конструктор для старых браузеров return function(ctor, superCtor) { var Tmp = function() {}; Tmp.prototype = superCtor.prototype; ctor.prototype = new Tmp(); ctor.prototype.constructor = ctor; }; })(); 

    UPD:

    In the implementations above, after assigning the prototype, the Function.prototype.constructor property is set. Although this property is rarely used in practice (I personally have never seen it in the production code), a full-fledged implementation of inheritance should expose it.

    • from the explanation it is generally not clear what is wrong with Bar.prototype = new Foo(); - something like if you do this, and at the same time this and that is all bad, and if there is no such and such this? - Grundy
    • In any case, with Bar.prototype = new Foo(); the constructor Foo is called twice . Even if his call has no side effects, one of the calls is completely meaningless . Well, the fact that now the designer Foo has no side effects does not mean that he will not have them in the future. I wouldn’t want to maintain code that could suddenly break. - Dmitriy Simushev
    • any code can break suddenly :) it is called twice only because we call it twice. If you call him once he will be called once :) - Grundy
    • @Grundy, updated the answer. As for the double call, then if you like to produce crutches - this is your right. In my opinion, it’s better to do everything right at once, especially considering the tiny overhead head in the Object.create call - Dmitriy Simushev
    • If everything was so simple :) - Grundy

    If you are using ES6, you can use the standard language tools for inheritance:

     // Базовый класс class Foo { doSomething() { // ... } } // Дочерний класс, наследующий все поведение базового + // методы определенные ниже. class Bar extends Foo { doAnotherAction() { // ... } }