Почему поля экземпляра принадлежат экземплярам, ​​а не их прототипу?

В синтаксисе 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, поэтому он дублирует существующую языковую функцию.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
1
100
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

class синтаксис был просто синтаксическим сахаром для существующих вещей:

  1. A prototype — объект с общими вещами между экземплярами, в основном методы и дескрипторы get-set.
  2. A constructor — функция для создания объектов с предопределенным прототипом
  3. Использование constructor в качестве пространства имен для хранения связанных вещей
  4. Поля? Какие поля? У вас просто есть объект с прототипом, делайте с ним что хотите

class синтаксис включал все это... за исключением констант в прототипе - они имеют очень странное поведение (на самом деле являются инициализаторами свойств), и никто никогда их не использовал

«у них очень странное поведение (на самом деле они являются инициализаторами свойств), и никто никогда их не использовал» - вы имеете в виду «константы в прототипе» или «поля класса»?

Bergi 03.04.2023 00:38

Я думаю, что «поля» относятся к этому, довольно недавнему добавлению, и я почти никогда не использую его, но, вероятно, многие другие найдут его полезным.

qrsngky 03.04.2023 04:08
Ответ принят как подходящий

До добавления открытых полей экземпляров эти свойства всегда принадлежали экземплярам (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);

Что касается того, почему эти свойства принадлежали экземплярам, ​​а не прототипам (также применимо до того, как classes стали частью JS):

Если у вас есть свойство в прототипе, как только вы присвоите, скажем, instance1.property1 = 3, значение из прототипа больше не используется (затенение свойства); в экземпляре создается новое свойство, а значение в прототипе не изменяется. Поэтому помещать значение в прототип не очень полезно.

Напротив, метод обычно не переназначается, и полезно использовать его совместно между экземплярами. instance1.method1(...) и instance2.method1(...) вызывают одну и ту же функцию с разными this. (Так что это не псевдонимы друг друга, поскольку они имеют разные эффекты).

«эти свойства всегда принадлежали экземплярам». - ну, если только он не был создан через MyClass.prototype.counter = 0. «Вводить значение в прототип не очень полезно». - иногда полезно в качестве значения по умолчанию. Но я согласен, это довольно неясное использование, в основном даже значение по умолчанию уже было создано для экземпляра для простоты (а также считается лучшей практикой создавать все свойства экземпляра уже во время создания экземпляра, а не когда-то позже).

Bergi 03.04.2023 15:40

@ Берги Верно. Под «принадлежит экземплярам» я имел в виду, что наиболее распространенная практика была похожа на this.counter = 0 внутри конструктора.

qrsngky 03.04.2023 15:44

Другие вопросы по теме