Я обнаружил интересное поведение расширений js и не понимаю причин этого в случае копирования значений сразу из другого значения, по некоторым причинам значение будет скопировано из родителя
class parent {
defaultValue = 1;
value = this.defaultValue;
}
new parent() // {defaultValue: 1, value: 1}
class child extends parent {
defaultValue = 2;
}
new child() // {defaultValue: 2, value: 1}
Что на самом деле для меня не очевидно и непонятно но если я заменю его функцией или даже геттером, поведение будет изменено, и я получу значение от дочернего
class parent {
get defaultValue() { return 1; }
value = this.defaultValue;
}
new parent() // {defaultValue: 1, value: 1}
class child extends parent {
get defaultValue() { return 2; }
}
new child() // {defaultValue: 2, value: 2}
Главный вопрос здесь заключается в том, почему в момент создания дочернего элемента в первом случае JS ищет значение родительского класса, а во втором случае JS ищет значение дочернего класса
Кто-нибудь может объяснить причину такого поведения?
РЕДАКТИРОВАТЬ Подробности см. в ответах t.niese или Юрия Тарабанко.
Краткий ответ выглядит следующим образом
Геттеры (также функция) и функция будут переопределены в прототипе, что позволит вызывать их родителем с дочерними изменениями (на самом деле это ожидается)
В то время как первый пример с присваиваемыми простыми значениями будет вызываться только в момент создания класса (конструктор или супер) и появится только в рамках текущего класса (который не может быть изменен дочерним элементом) и прототипа (который может быть изменен дочерним )
Назначение JS присваивает значение. Это не указатель/ссылка
@evolutionxbox это не вопрос, я определяю новое значение/ссылку/или то, что вы хотите в дочернем классе, как новое значение, но оно все еще использовалось от родителя, если оно все еще связано с ссылочными значениями, можете ли вы объяснить это подробно
Почему вы вставляете ответ в вопрос?
Это происходит потому, что геттеры определены в прототипах, а свойства экземпляра определены в экземпляре (как следует из названия).
Итак, когда экземпляр Child1 создается, он сначала определяет свойства из Parent1, и вы получаете defaultValue = 1
Напротив, при создании экземпляра Child2 у Child2.prototype уже будет переопределено свойство defaultValue.
class Parent1 {
defaultValue = 1;
value = this.defaultValue;
}
class Child1 extends Parent1 {
defaultValue = 2;
}
class Parent2 {
get defaultValue() { return 1; }
value = this.defaultValue;
}
class Child2 extends Parent2 {
get defaultValue() { return 2; }
}
console.info(Object.hasOwn(new Child1(), 'defaultValue'))
console.info(Object.hasOwn(new Child2(), 'defaultValue'))
Все еще немного странно (с точки зрения других языков), что parent имеет доступ к child функциям на этапе инициализации. Так что я абсолютно понимаю, что такое поведение не ожидается.
@t.niese Я думаю, все это происходит потому, что в js нет классов. Просто синтаксический сахар для прототипов. И, к сожалению, js-разработчик должен помнить об этом. Например, class C{ [Math.random()] = Math.random(); } немного странно, потому что имя реквизита не случайное :)
Связанный с этим вопрос: как получить доступ к переопределенным функциям родительского класса в коде родительского класса.
Геттеры и сеттеры — это функции, которые определены с определением класса, поэтому в конструкторе класса parent (и инициализации полей его класса экземпляра) вы можете вызвать функцию, которая существует только в child (что действительно может быть немного странный):
class parent {
value = this.test();
constructor() {
this.test()
}
}
class child extends parent {
test() {
console.info('test')
}
}
new child()
Итак, какая функция (или геттер/сеттер) вызывается, уже определена в определении класса до того, как будет выполнено создание экземпляра.
С другой стороны, поля класса открытого экземпляра инициализируются/устанавливаются на этапе инициализации экземпляра в определенном порядке (показанный код может работать только в браузерах на основе Chrome):
class parent {
defaultValue = (() => {
console.info('parent:init defaultValue')
return 1;
})();
value = (() => {
console.info('parent:init value')
return this.defaultValue;
})();
constructor() {
console.info('parent constructor')
}
}
class child extends parent {
defaultValue = (() => {
console.info('child:init defaultValue')
return 2;
})();
constructor() {
console.info('child constructor before super()')
super()
console.info('child constructor after super()')
}
}
new child()
В вашем первом примере создание и инициализация поля общедоступного экземпляра с именем defaultValue в Child происходит после создания и инициализации поля общедоступного экземпляра с именем value в Parent.
Итак: несмотря на то, что значение this в инициализаторе поля публичного экземпляра с именем value в Parent будет указывать на экземпляр Child в процессе создания, дочернее локальное поле публичного экземпляра с именем defaultValue еще не существует, поэтому цепочка прототипов выполняется. до свойства с именем defaultValue в экземпляре Parent, что дает 1.
В вашем последнем примере у вас есть функции получения с именем defaultValue.
Функции-получатели, указанные таким образом, даже если их API преднамеренно выглядит как API открытых полей экземпляра, в конечном итоге станут функциями в [[Prototype]] любого строящегося экземпляра.
Объекты [[Prototype]] экземпляров создаются во время объявления класса (т. е. всегда до того, как что-либо инициируется созданием экземпляра), как свойство .prototype класса (или функция-конструктор); ссылки на эти объекты затем копируются в [[Prototype]] любого строящегося экземпляра в качестве первого шага в создании объекта (подумайте Object.create(class.prototype)).
Итак, this.defaultValue из Parent общедоступного инициализатора экземпляра для value разрешается в геттер на [[Prototype]] экземпляра Child в процессе создания, который является функцией, возвращающей 2.
Пожалуйста, не загружайте изображения кода . Их нельзя скопировать, чтобы воспроизвести выпуск, их невозможно найти для будущих читателей, и их труднее читать, чем текст. Пожалуйста, опубликуйте фактический код в виде текста, чтобы создать минимальный воспроизводимый пример.