Каким образом свойство в прототипе участвует в записи
В интернете много где, например, тут, утверждается, что при записи свойства в объект, даже если такого свойства в объекте еще нет, но есть в прототипе, свойство просто создастся в объекте без участия прототипа. То есть предполагается, что если есть
const animal = {
name: 'somename',
};
const cat = {
__proto__: animal,
};
и я сделаю cat.name = 'Васька', то name из animal вообще никак не будет задействован.
Однако если свойству name у animal поставить дескриптор writable = false, то при записи свойства у cat возникнет ошибка.
'use strict';
const animal = {
name: 'somename',
};
const cat = {
__proto__: animal,
};
Object.defineProperty(animal, 'name', { writable: false });
cat.name = 'Васька';
console.log(cat);
Объясните, пожалуйста, какая все-таки очередность действий происходит при такой записи.
Ответы (1 шт):
Рассмотрим, что происходит при выполнении оператора присваивания
В случае обычного присваивания вызывается внутренний метод PutValue, в который параметрами передается новое значение, и куда его нужно установить.
Если погрузиться в алгоритм PutValue станет интересен пункт 6
b. Пусть succeeded будет результатом выполнения ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)). c. Если succeeded равно false и строгий режим, тогда кинуть TypeError исключение.
Как можно заметить, если внутренний метод [[Set]] вернет false, будет кинуто исключение, что и происходит в случае кода в вопросе.
Чтобы разобраться, почему возвращается false нужно погрузиться немного глубже, а именно в метод OrdinarySet, который получает текущий дескриптор свойства в объекте, если он есть и передает в OrdinarySetWithOwnDescriptor
В случае, если переданный дескриптор свойства - undefined, и у объекта есть prototype - вернется результат вызова [[Set]] у прототипа, с переданным Receiver - который ссылается на текущий объект.
В случае если у объекта нет прототипа просто создается PropertyDescriptor со значением по умолчанию
{ [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }
Важно обратить внимание на значение [[Writable]]: true.
Далее проверяем, что полученный PropertyDescriptor является DataProperty, то есть у него есть хотя бы одно из свойств [[Value]], [[Writable]], в этом случае происходит следующее:
- если
[[Writable]]: false- вернутьfalse - если Type(Receiver) не
Object, вернутьfalse - проверяет наличие нужного свойства у
Receiverи если оно есть- проверяется значение
[[Writable]]: если[[Writable]]: false- вернутьfalse - устанавливается новое значение свойства
- проверяется значение
- если данного свойства нет:
- просто создается данное свойство у Receiver.
Рассмотрим что происходит в коде в вопросе:
попадаем в метод
OrdinarySetWithOwnDescriptor, в котором параметрownDescпринимает значениеundefined, так как такого свойства нет в объектеcat. В качествеReceiverпередается сам объектcatтак как
ownDesc == undefined, но уcatесть прототипanimalберем его и вызываем[[Set]]уже для него ипопадаем в метод
OrdinarySetWithOwnDescriptor, в котором параметрownDescпринимает значение{value: 'somename', writable: false, enumerable: true, configurable: true}`так как нужное свойство есть в объекте
animal. В качествеReceiverвсе еще передается объектcat.ownDescявляетсяDataProperty, а значит происходит переход к следующей проверке[[Writable]]: false- в данном случае поле ужеwritable: false- поэтому происходит возвратfalse, как результат - ошибка в строгом режиме.