Каково значение свойства конструктора Javascript?

Попытка с головой окунуться в объектно-ориентированный подход Javascript... и, как и многие другие, столкнуться с замешательством по поводу свойства constructor. В частности, значение свойства constructor, поскольку я не могу заставить его иметь какой-либо эффект. Например.:

function Foo(age) {
    this.age = age;
}

function Bar() {
    Foo.call(this, 42);
    this.name = "baz"; 
}

Bar.prototype = Object.create(Foo.prototype); 
var b = new Bar;    

alert(b.constructor); // "Foo". That's OK because we inherit `Foo`'s prototype.
alert(b.name);        // "baz". Shows that Bar() was called as constructor.
alert(b.age);         // "42", inherited from `Foo`.

В приведенном выше примере объект b, по-видимому, имеет правильный конструктор с именем (Bar) и наследует свойство age от Foo. Так почему же многие люди предлагают это как необходимый шаг:

Bar.prototype.constructor = Bar;

Очевидно, что правильный конструктор Bar был вызван при создании b, так какое влияние оказывает это свойство прототипа? Мне любопытно узнать, какое практическое значение на самом деле имеет «правильная» установка свойства конструктора, поскольку я не вижу никакого влияния на то, какой конструктор фактически вызывается после создания объекта.


person aaa90210    schedule 25.10.2010    source источник
comment
Я объяснил связь между прототипом и конструктором по адресу: stackoverflow.com/a/23877420/895245.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 05.09.2015


Ответы (6)


Обновление за сентябрь 2020 г.

Ответ ниже относится ко временам ECMAScript 3, и первое предложение больше не соответствует действительности, потому что, начиная с ECMAScript 6, свойство constructor используется в нескольких местах. Тем не менее, я думаю, что общая суть все еще применима. Спасибо TJ Crowder за указание на это в комментариях, и, пожалуйста, прочитайте его ответ, чтобы получить более полное представление о текущей ситуации.

Исходный ответ

Свойство constructor не имеет абсолютно никакого практического значения ни для чего внутри. Это бесполезно только в том случае, если ваш код явно использует его. Например, вы можете решить, что вам нужно, чтобы каждый из ваших объектов имел ссылку на фактическую функцию-конструктор, которая его создала; если это так, вам нужно будет явно установить свойство constructor при настройке наследования, назначив объект свойству prototype функции-конструктора, как в вашем примере.

person Tim Down    schedule 25.10.2010
comment
constructor является важной частью возможности прокладки Object.getPrototypeOf. Я лично использую Object.getPrototypeOf и предполагаю, что никто не нарушает отношения prototype ‹-› constructor - person Raynos; 13.04.2012
comment
@Raynos: не самое безопасное предположение, но, вероятно, безопаснее, чем раньше. Вернитесь на несколько лет назад, и почти все учебники по наследованию объектов JavaScript в Интернете были основаны на Java и рекомендовали вручную устанавливать свойство constructor. У нас с вами совершенно разные идеалы: я предпочитаю обертки прокладкам и живу без Object.getPrototypeOf так долго, что теперь не горю желанием. - person Tim Down; 13.04.2012
comment
Суть в том, что мы должны предупредить людей, что для соответствия ES5 эта связь не должна нарушаться в старых браузерах. - person Raynos; 13.04.2012
comment
@Raynos: я думаю, что я бы подумал о том, чтобы полагаться на свойство constructor, которое, как обсуждалось, имеет историю вмешательства в сценарии и в остальном бесполезно, неприемлемое требование для любой прокладки и не рекомендовал бы использовать его в целом. - person Tim Down; 13.04.2012
comment
@Raynos: Мы должны обсуждать эти вопросы в чате, но это отстой. - person Tim Down; 13.04.2012
comment
Эффективно работают асинхронные разговоры @TimDown в chat.SO через пинги, также эффективно работают электронные письма. Основная проблема здесь заключается в том, что эти отношения являются важной частью понимания того, что constructor и prototype являются двумя сторонами одной медали, и эти отношения не должны нарушаться. - person Raynos; 13.04.2012
comment
Спустя много-много лет после того, как оно было первоначально написано, ваше первое предложение уже не соответствует действительности. constructor теперь используется для операций в спецификации JavaScript, таких как здесь, здесь, здесь и здесь. - person T.J. Crowder; 02.09.2020
comment
@T.J.Crowder: Ну-ну-ну. Я не следил за последними изменениями языка. В любом случае, я не думаю, что когда-либо использовал constructor. Спасибо за обновление, я добавлю примечание к ответу. - person Tim Down; 02.09.2020

Первый шаг — понять, что такое constructor и prototype. Это несложно, но от "наследства" в классическом понимании надо отказаться.

Конструктор

Свойство constructor не вызывает никаких особых эффектов в вашей программе, за исключением того, что вы можете посмотреть на него, чтобы увидеть, какая функция использовалась в сочетании с оператором new для создания вашего объекта. Если вы набрали new Bar(), это будет Bar, а вы набрали new Foo, это будет Foo.

Прототип

Свойство prototype используется для поиска в случае, если рассматриваемый объект не имеет запрошенного свойства. Если вы напишете x.attr, JavaScript попытается найти attr среди атрибутов x. Если он не сможет его найти, он будет искать в x.__proto__. Если его там тоже нет, он будет искать в x.__proto__.__proto__ и так далее, пока определено __proto__.

Так что же такое __proto__ и какое отношение он имеет к prototype? Короче говоря, prototype для «типов», а __proto__ для «экземпляров». (Я говорю это в кавычках, потому что на самом деле нет никакой разницы между типами и экземплярами). Когда вы пишете x = new MyType(), происходит (среди прочего) то, что x.__proto___ устанавливается на MyType.prototype.

Вопрос

Теперь вышеизложенное должно быть всем, что вам нужно, чтобы понять, что означает ваш собственный пример, но попытаться ответить на ваш фактический вопрос; "зачем писать что-то вроде":

Bar.prototype.constructor = Bar;

Лично я никогда этого не видел и нахожу это немного глупым, но в контексте, который вы указали, это будет означать, что объект Bar.prototype (созданный с использованием new Foo(42)) будет представляться как созданный Bar, а не Foo. Я предполагаю, что идея состоит в том, что некоторые делают что-то похожее на C++/Java/C#-подобные языки, где поиск типа (свойство constructor) всегда будет давать наиболее конкретный тип, а не тип более общего объекта дальше в прототипе. цепь.

Мой совет: не думайте слишком много о «наследовании» в JavaScript. Концепции интерфейсов и примесей имеют больше смысла. И не проверяйте объекты на их типы. Вместо этого проверьте необходимые свойства («если он ходит как утка и крякает как утка, это утка»).

Попытка втиснуть JavaScript в классическую модель наследования, когда все, что у него есть, — это механизм прототипов, как описано выше, — вот что вызывает путаницу. Многие люди, которые предлагали вручную установить свойство constructor, вероятно, пытались сделать именно это. Абстракции — это хорошо, но это ручное присвоение свойства конструктора не очень идиоматичное использование JavaScript.

person Jakob    schedule 25.10.2010
comment
Я согласен с тем, что свойство constructor не очень полезно, но наследование от прототипа другого объекта может быть чрезвычайно мощным, и я бы нисколько не препятствовал этому. - person Tim Down; 25.10.2010
comment
Спасибо - это может быть не идиоматично, но многие учебные пособия в сети (и вопросы и ответы по SE) предполагают, что использование этого конкретного метода «необходимо» - как будто вся модель прототипа ломается, если вы ее не используете. - person aaa90210; 25.10.2010
comment
О, извините, если так получилось. Наследование от прототипа другого объекта не является проблемой; изменение свойства конструктора. - person Jakob; 25.10.2010
comment
Является ли __proto__ кроссбраузерным? - person Camilo Martin; 22.09.2012
comment
Если вы посмотрите на goog.inherits в code.google. com/p/closure-library/source/browse/closure/goog/, вы увидите это прямо здесь: childCtor.prototype.constructor = childCtor; Я думаю, что это имеет смысл, потому что для меня неожиданно, что у вас будет var b = new B(), а затем < б>нет b.constructor === B. Что мне не хватает? - person Dmitry Minkovsky; 08.03.2013
comment
@Jakob, вы говорите, что свойство constructor не вызывает каких-либо особых эффектов в вашей программе, за исключением того, что вы можете посмотреть на него, чтобы увидеть, какая функция использовалась вместе с оператором new для создания вашего объекта. Если вы набрали new Bar(), это будет Bar, а вы набрали new Foo, это будет Foo. Это неправильно. Свойство constructor относится к функции, которая создала прототип данного объекта, как видно из примера даже OP. - person βξhrαng; 11.06.2013
comment
вы можете просмотреть его, чтобы увидеть, какая функция использовалась в сочетании с оператором new для создания вашего объекта. Нет, ты не можешь. Он также дает подсказку о том, с какой функцией сначала был связан объект-прототип (при условии, что для свойства constructor не установлено какое-либо другое значение). Он ничего не говорит вам наверняка об экземпляре, который его наследует. - person RobG; 20.05.2014
comment
Спустя много-много лет после того, как оно было первоначально написано, свойство constructor не вызывает никаких особых эффектов в вашей программе больше не соответствует действительности. constructor теперь используется для операций в спецификации JavaScript, таких как здесь, здесь, здесь и здесь. - person T.J. Crowder; 02.09.2020

один случай использования конструктора:

  1. это одна из распространенных реализаций наследования:

    Function.prototype.extend = function(superClass,override) {
        var f = new Function();
        f.prototype = superClass.prototype;
        var p = this.prototype = new f();
        p.constructor = this;
        this.superclass = superClass.prototype;
        ...
    };
    
  2. этот new f() не будет вызывать конструктор суперкласса, поэтому, когда вы создаете подкласс, возможно, вам нужно сначала вызвать суперкласс, например:

    SubClass = function() {
        SubClass.superClass.constructor.call(this);
    };
    

поэтому свойство конструктора имеет смысл здесь.

person Jack Hu    schedule 27.03.2012
comment
Да, но вы можете написать SubClass.superClass.call(this);. - person Ali Shakiba; 08.04.2015
comment
Если вы не планируете ссылаться на свойство конструктора в экземпляре, вы можете безопасно прокомментировать строку p.constructor = this, и ничего не изменится. Ответ может продемонстрировать случай, когда используется свойство constructor, но не объясняет, почему оно там используется. Короче говоря, этот ответ не отвечает на исходный вопрос о значении свойства constructor. - person golem; 21.01.2016
comment
1. Не могли бы вы написать простой пример использования? 2. Какова роль override? - person OfirD; 09.11.2016

Предыдущие ответы здесь говорят (по-разному), что значение свойства constructor ничем не используется в самом JavaScript. Это было правдой, когда эти ответы были написаны, но ES2015 и последующие начали использовать constructor для вещей.

Свойство constructor свойства prototype функции предназначено для указания на функцию, чтобы вы могли спросить объект, что сконструировало его. Он настраивается автоматически как часть создания традиционного объекта-функции или объекта-конструктора класса (подробности). .

function TraditionalFunction() {
}

console.log(TraditionalFunction.prototype.constructor === TraditionalFunction); // true

class ExampleClass {
}

console.log(ExampleClass.prototype.constructor === ExampleClass); // true

У стрелочных функций нет свойства prototype, поэтому у них нет prototype.constructor.

В течение многих лет спецификация JavaScript только говорила, что свойство constructor будет там и будет иметь это значение (обратная ссылка на функцию) по умолчанию. Но начиная с ES2015 это изменилось, и теперь различные операции в спецификации фактически используют свойство constructor, например this, это, это и это.

Поэтому при настройке функций-конструкторов, которые строят цепочки наследования, лучше убедиться, что свойство constructor ссылается на соответствующую функцию. См. мой ответ здесь для примеров и т. д.

person T.J. Crowder    schedule 02.09.2020

Один из случаев использования, когда вы хотите, чтобы свойство prototype.constructor выдержало переназначение свойства prototype, — это когда вы определяете метод для prototype, который создает новые экземпляры того же типа, что и данный экземпляр. Пример:

function Car() { }
Car.prototype.orderOneLikeThis = function() {  // Clone producing function
    return new this.constructor();
}
Car.prototype.advertise = function () {
    console.log("I am a generic car.");
}

function BMW() { }
BMW.prototype = Object.create(Car.prototype);
BMW.prototype.constructor = BMW;              // Resetting the constructor property
BMW.prototype.advertise = function () {
    console.log("I am BMW with lots of uber features.");
}

var x5 = new BMW();

var myNewToy = x5.orderOneLikeThis();

myNewToy.advertise(); // => "I am BMW ..." if `BMW.prototype.constructor = BMW;` is not 
                      // commented; "I am a generic car." otherwise.
person golem    schedule 21.01.2016
comment
Почему вы не предпочли Object.setPrototypeOf(BMW.prototype, Car.prototype); вместо BMW.prototype = Object.create(Car.prototype);? - person overexchange; 30.10.2017
comment
@overexchange Да, это уменьшит шаг конструктора настроек. Мы должны использовать это право ?? - person Suraj Jain; 15.04.2019

Свойство конструктора указывает на конструктор, который использовался для создания экземпляра объекта. Если вы ввели «new Bar()», это будет «Bar», а вы ввели «new Foo()», это будет «Foo».

Но если вы установите прототип без установки конструктора, вы получите что-то вроде этого:

function Foo(age) {
    this.age = age;
}

function Bar() {
    this.name = "baz"; 
}

Bar.prototype = new Foo(42); 
var one = new Bar();
console.log(one.constructor);   // 'Foo'
var two = new Foo();
console.log(two.constructor);   // 'Foo'

Чтобы фактически установить конструктор на конструктор, который использовался для создания объекта, нам также нужно установить конструктор при установке прототипа следующим образом:

function Foo(age) {
    this.age = age;
}

function Bar() {
    this.name = "baz"; 
}

Bar.prototype = new Foo(42); 
Bar.prototype.constructor = Bar;
var one = new Bar();
console.log(one.constructor);   // 'Bar'
var two = new Foo();
console.log(two.constructor);   // 'Foo'
person jsbisht    schedule 08.12.2015