В синтаксисе JavaScript class
, представленном в ES6 (статические свойства доступны из класса, свойства экземпляра доступны из экземпляров):
this
свойства принадлежат экземплярам.Поэтому я ожидал, что поля экземпляра будут принадлежать прототипу экземпляра, как и методы экземпляра. Но это не так, они получают специальную обработку и вместо этого принадлежат экземплярам, как this
свойства.
Например, класс
class A {
static x; // static field
static f() {} // static method
y; // instance field (special treatment)
g() {} // instance method
constructor() {this.z = 0} // this property
}
дает результаты
> Object.getOwnPropertyNames(A)
[ 'length', 'name', 'prototype', 'f', 'x' ]
> Object.getOwnPropertyNames(A.prototype)
[ 'constructor', 'g' ]
> Object.getOwnPropertyNames(new A)
[ 'y', 'z' ]
Почему поля экземпляра принадлежат экземплярам, а не их прототипу?
Недостаток, который я вижу в этом специальном подходе, заключается в том, что он не позволяет просто объявлять нефункциональные свойства в прототипе экземпляров, вынуждая делать это после объявления класса (или прибегая к статическому блоку инициализации):
> A.prototype.y2 = 0;
0
> Object.getOwnPropertyNames(A.prototype)
[ 'constructor', 'g', 'y2' ]
Еще один недостаток заключается в том, что если кто-то хочет объявить свойства экземпляров, для этого уже есть this
, поэтому он дублирует существующую языковую функцию.
class
синтаксис был просто синтаксическим сахаром для существующих вещей:
prototype
— объект с общими вещами между экземплярами, в основном методы и дескрипторы get-set.constructor
— функция для создания объектов с предопределенным прототипомconstructor
в качестве пространства имен для хранения связанных вещейclass
синтаксис включал все это... за исключением констант в прототипе - они имеют очень странное поведение (на самом деле являются инициализаторами свойств), и никто никогда их не использовал
«у них очень странное поведение (на самом деле они являются инициализаторами свойств), и никто никогда их не использовал» - вы имеете в виду «константы в прототипе» или «поля класса»?
Я думаю, что «поля» относятся к этому, довольно недавнему добавлению, и я почти никогда не использую его, но, вероятно, многие другие найдут его полезным.
До добавления открытых полей экземпляров эти свойства всегда принадлежали экземплярам (except in some rather obscure usage). И после добавления они продолжали хранить эти свойства в экземплярах.
В разделе 2.1 этой статьи доктора Акселя Раушмайера показана мотивация добавления публичных полей экземпляра.
Иногда у вас есть назначение в конструкторе, которое создает свойство экземпляра, но на него не влияют никакие другие данные в конструкторе (например, параметр).
class MyClass {
constructor() {
this.counter = 0;
}
}
В таком случае вы можете использовать поле для переноса создания счетчика из конструктора:
class MyClass {
counter = 0;
constructor() {
}
}
Таким образом, добавление публичных экземпляров может упростить конструкторы. Это также дает понять, что начальное значение свойства не зависит от аргументов конструктора. (Это также может улучшить читабельность и упростить документацию)
Существует также семантическая разница, поскольку поля класса используют семантику [[DefineProperty]]
(с поведением, соответствующим Object.defineProperty
), поэтому с общедоступными полями установщик в суперклассе не будет вызываться.
Сравнивать:
class A {
set prop(value) {
console.info('SETTER: '+value);
}
}
class B extends A {
constructor() {
super();
this.prop = 123; // (setter will be called, inherited from A)
}
}
console.info(new B); //does not contain a 'prop' field because there is a setter
с
class A {
set prop(value) {
console.info('SETTER: '+value);
}
}
class B extends A {
prop = 123; //setter from A is not called
}
console.info(new B);
Что касается того, почему эти свойства принадлежали экземплярам, а не прототипам (также применимо до того, как class
es стали частью JS):
Если у вас есть свойство в прототипе, как только вы присвоите, скажем, instance1.property1 = 3
, значение из прототипа больше не используется (затенение свойства); в экземпляре создается новое свойство, а значение в прототипе не изменяется. Поэтому помещать значение в прототип не очень полезно.
Напротив, метод обычно не переназначается, и полезно использовать его совместно между экземплярами. instance1.method1(...)
и instance2.method1(...)
вызывают одну и ту же функцию с разными this
. (Так что это не псевдонимы друг друга, поскольку они имеют разные эффекты).
«эти свойства всегда принадлежали экземплярам». - ну, если только он не был создан через MyClass.prototype.counter = 0
. «Вводить значение в прототип не очень полезно». - иногда полезно в качестве значения по умолчанию. Но я согласен, это довольно неясное использование, в основном даже значение по умолчанию уже было создано для экземпляра для простоты (а также считается лучшей практикой создавать все свойства экземпляра уже во время создания экземпляра, а не когда-то позже).
@ Берги Верно. Под «принадлежит экземплярам» я имел в виду, что наиболее распространенная практика была похожа на this.counter = 0
внутри конструктора.
Комментарии перемещены в чат ; пожалуйста, не продолжайте обсуждение здесь. Прежде чем публиковать комментарий под этим, пожалуйста, ознакомьтесь с целями комментариев . Комментарии, которые не требуют разъяснений или предложений по улучшению, обычно относятся к ответу , к Meta Stack Overflow или в чату переполнения стека. Комментарии, продолжающие обсуждение, могут быть удалены.