Почему объекты, имеющие экземпляр класса с частной собственностью в прототипе, выбрасывают при доступе к частному члену?

Рассмотрим этот код:

class Klass {
  #priv = 42

  get pub() {
    return this.#priv
  }
}

// these two correctly return 42
console.info(new Klass().pub);
console.info((new class extends Klass {}).pub)

// but this one throws "TypeError: Cannot read private member 
// #priv from an object whose class did not declare it"
console.info(Object.create(new Klass()).pub) // throws 

// even though
console.info(Object.create(new Klass()) instanceof Klass) // true
console.info(Object.getPrototypeOf(Object.create(new Klass())).pub) // 42

Я подумал, что, поскольку у меня есть настоящий экземпляр Klass в цепочке прототипов, доступ к Object.create(new Klass()).pub не выдаст ошибку.

Это задумано? И если да, то почему так сделано? Кроме того, как правильно использовать универсальную функцию для клонирования экземпляров произвольных классов, которые ведут себя аналогично Klass?

Контекст:

Я столкнулся с этой проблемой, когда что-то тестировал с помощью vitest. Мой код выглядел примерно так:

import { it, expect } from 'vitest';

class Klass {
    #priv = 42;

    get pub() {
        return this.#priv;
    }
}

it('works', () => {
    expect(new Klass()).toMatchObject({
        make_this_test_fail: 'yup',
        pub: 42,
    });
});

Мой тест, очевидно, провалился, но вместо того, чтобы дать мне хорошую разницу, показывающую, что свойство make_this_test_fail не было найдено в показанном экземпляре

 FAIL  test/the.test.js [ test/the.test.js ]
TypeError: Cannot read private member #priv from an object whose class did not declare it
 ❯ Klass.get pub [as pub] test/the.test.js:7:15
      5| 
      6|  get pub() {
      7|   return this.#priv;
       |               ^
      8|  }
      9| }

Я отследил это до этой строки, откуда actual происходит deepClone, которая создает экземпляры произвольных классов, используя Object.create здесь.

Я подозреваю, что Object.create() не воспроизводит все, что необходимо для ведения бухгалтерского учета класса, обеспечивающего доступ к частной собственности.

Barmar 11.07.2024 22:09

Я тоже так думаю, но надеюсь, что кто-нибудь здесь сможет пролить больше света на этот вопрос :). Я также хотел бы знать, есть ли более «пуленепробиваемый» способ клонирования произвольных экземпляров классов, чтобы такие доступы не вызывали ошибок (я только что отредактировал вопрос, чтобы отразить это)

Bartosz Gościński 11.07.2024 22:12

Пробовали structuredClone()?

Barmar 11.07.2024 22:13

Да, но это дает пустой объект. Я думаю, что он повторяет только свои свойства, но pub является геттером прототипа, поэтому он не «достигает» его. structuredClone(new class Klass { #priv = 42; get pub() { return this.#priv; }})

Bartosz Gościński 11.07.2024 22:16

Верно. По сути, я думаю, что большая часть традиционных объектных средств не предназначена для работы с расширенными функциями классов. Это уже не просто синтаксический сахар.

Barmar 11.07.2024 22:19
Поведение ключевого слова "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) для оценки ваших знаний,...
3
6
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это задумано? И если да, то почему так сделано?

Да, так задумано: частные поля не наследуются по цепочке прототипов, они существуют только в том конкретном экземпляре, в котором они были созданы во время создания. Это делает их совершенно незаметными снаружи, вы не можете вмешиваться в приватные поля, например. обмениваясь прототипом объекта, и вы не можете обманом заставить метод создать частное поле (по назначению, рассмотрите установщик в вашем примере) на объекте, где оно ранее не существовало.

Частные поля являются «жестко приватными» и очень похожи на внутренние слоты встроенных объектов. Они также не пересылаются через прокси. Ментальная модель частного поля подобна WeakMap хранению значения для каждого экземпляра.

(Тем не менее не все согласны с тем, что это хороший дизайн.)

Кроме того, как правильно использовать универсальную функцию для клонирования экземпляров произвольных классов, которые ведут себя аналогично Klass?

Вы не можете написать такую ​​общую функцию. Любой класс, независимо от того, использует ли он частные поля или нет, может иметь частное состояние, которое невозможно клонировать. Единственный способ добиться этой цели — дать каждому классу метод clone для клонирования себя соответствующим образом. Используйте символ для имени метода, если хотите избежать конфликтов.

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