Расширяет поведение в js со значением по умолчанию

Я обнаружил интересное поведение расширений 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 или Юрия Тарабанко.

Краткий ответ выглядит следующим образом

Геттеры (также функция) и функция будут переопределены в прототипе, что позволит вызывать их родителем с дочерними изменениями (на самом деле это ожидается)

В то время как первый пример с присваиваемыми простыми значениями будет вызываться только в момент создания класса (конструктор или супер) и появится только в рамках текущего класса (который не может быть изменен дочерним элементом) и прототипа (который может быть изменен дочерним )

Пожалуйста, не загружайте изображения кода . Их нельзя скопировать, чтобы воспроизвести выпуск, их невозможно найти для будущих читателей, и их труднее читать, чем текст. Пожалуйста, опубликуйте фактический код в виде текста, чтобы создать минимальный воспроизводимый пример.

adiga 18.11.2022 11:37

Назначение JS присваивает значение. Это не указатель/ссылка

evolutionxbox 18.11.2022 11:42

@evolutionxbox это не вопрос, я определяю новое значение/ссылку/или то, что вы хотите в дочернем классе, как новое значение, но оно все еще использовалось от родителя, если оно все еще связано с ссылочными значениями, можете ли вы объяснить это подробно

vinger 18.11.2022 11:46

Почему вы вставляете ответ в вопрос?

evolutionxbox 19.11.2022 15:48
Как использовать API парсинга квитанций с помощью JavaScript за 5 минут?
Как использовать API парсинга квитанций с помощью JavaScript за 5 минут?
В этом руководстве вы узнаете, как использовать API парсинга квитанций за 5 минут с помощью JavaScript. Eden AI предоставляет простой и удобный для...
Хук useOnClickOutside в ReactJS
Хук useOnClickOutside в ReactJS
Как разработчик ReactJS, вы, возможно, сталкивались с ситуацией, когда вам нужно закрыть модальное или выпадающее меню, когда кто-то щелкает за его...
Хуки (часть-2) - useEffect
Хуки (часть-2) - useEffect
Хук useEffect - один из самых мощных и универсальных инструментов в арсенале разработчика React. Он позволяет вам управлять побочными эффектами в...
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React
В предыдущем посте я показал вам на примерах, как писать базовые тесты в React. Важнейшей частью пользовательского интерфейса приложений является...
Как конвертировать HTML в PDF с помощью jsPDF
Как конвертировать HTML в PDF с помощью jsPDF
В этой статье мы рассмотрим, как конвертировать HTML в PDF с помощью jsPDF. Здесь мы узнаем, как конвертировать HTML в PDF с помощью javascript.
Создайте титры как в звездных войнах с помощью CSS и Javascript
Создайте титры как в звездных войнах с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
4
5
88
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это происходит потому, что геттеры определены в прототипах, а свойства экземпляра определены в экземпляре (как следует из названия).

Итак, когда экземпляр 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 18.11.2022 16:29

@t.niese Я думаю, все это происходит потому, что в js нет классов. Просто синтаксический сахар для прототипов. И, к сожалению, js-разработчик должен помнить об этом. Например, class C{ [Math.random()] = Math.random(); } немного странно, потому что имя реквизита не случайное :)

Yury Tarabanko 18.11.2022 17:03
Ответ принят как подходящий

Связанный с этим вопрос: как получить доступ к переопределенным функциям родительского класса в коде родительского класса.

Геттеры и сеттеры — это функции, которые определены с определением класса, поэтому в конструкторе класса 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.

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

Понимание вывода/сужения типа TS с помощью комбинации расширений и реализации
Наследование в java не обнаруживает расширенный класс из другого пакета с таким же именем
Расширение L.control.zoom с помощью пользовательской кнопки
Вложенное тройное условие внутри Typescript расширяет тип
Что на самом деле делает «extensions»?
Хорошая практика использования класса с вложенными статическими классами, которые затем расширяют частные абстрактные классы пакета с помощью более статических методов, чтобы оставаться организованными?
Разница в использовании многоуровневого наследования в scala и использовании всех уровней на 1 уровне с помощью ключевого слова «с»
Как использовать сеттеры в расширенных классах ImmutableJS Record
Расширьте класс детектора устройств matomo/piwik, чтобы добавить новый клиентский синтаксический анализатор (проблема с пространством имен)
Как добавить параметр в mouselistener