Как доступ к собственности осуществляется под капотом
Примечание. Этот пост является несколько сложным и предполагает, что вы имеете некоторое представление о том, что такое [[Prototype]]
и как оно работает.
Если вы работали с JavaScript, вы, скорее всего, сталкивались с объектами и доступом к свойствам. Вот как это выглядит
// object literal const obj = { x: 52 }; // property access obj.x;
obj.x
называется доступом к собственности. Этот фрагмент кода фактически выполняет [[Get]]
операцию с obj
. Давайте посмотрим, как это работает
[[Получить]]
Встроенная операция сначала проверяет, имеет ли рассматриваемый объект свойство или нет. Если свойство найдено в объекте, возвращается значение. Итак, если мы рассмотрим приведенный выше пример, он вернет 52.
console.log(obj.x); //52
Довольно просто и понятно. Однако, если свойство не найдено в объекте, алгоритм [[Get]]
просматривает цепочку [[Prototype]]
, чтобы увидеть, сможет ли он найти свойство там. Если каким-либо образом не удается найти значение запрашиваемого свойства, возвращается значение undefined
.
Примечание: Обсуждение в цепочке [[Prototype]]
длинное, и я поступил бы несправедливо, если бы попытался втиснуть его сюда. Я сохраню это для следующего поста. На данный момент, если вы не знакомы с цепочкой [[Prototype]]
, просто знайте, что каждый объект имеет свойство с именем [[Prototype]]
, которое создается при создании объекта, и ему назначается ссылка на другой объект. Вершина каждой [[Prototype]]
цепочки является прототипом базового типа Object
. Я знаю, это может сбить с толку, если вы не знакомы с этим, но обещаю, я расскажу об этом в одной из следующих публикаций.
Давайте разберемся с этим и рассмотрим несколько примеров, чтобы увидеть, как это работает.
[[Get]]
сначала просматривает объект, чтобы найти значение. В этом случае свойство найдено и значение будет возвращено.
const obj = { x: 52 }; obj.x; //52
2. Если он не может найти свойство в объекте, он проходит по цепочке [[Prototype]]
, чтобы найти значение. В следующем примере Object.create()
создает новый объект, который [[Prototype]]
связан с переданным исходным объектом. mObj
не имеет свойства x
на нем, но x
существует в [[ Прототип]] цепочка.
const obj = { x: 52 }; const mObj = Object.create(obj); mObj.x; // 52;
3. Если каким-либо образом не удается найти запрашиваемое свойство, возвращается значение undefined
.
const obj = { x: 52 }; const mObj = Object.create(obj); mObj.x; // 52 mObj.y; // undefined
Примечание. Под капотом операция [[Get]]
сделала немного больше «работы» для mObj.y
, потому что ей пришлось просмотреть все [[Prototype]]
ссылки, чтобы попытаться найти y
.
[[Ставить]]
Излишне говорить, что если есть оператор для получения значения свойства, очевидно, что существует оператор для установки значения свойства. Рассмотрим следующий пример
const obj = { x: 52 }; obj.x = 32; obj.x; // 32
Теперь возникает соблазн подумать, что при присвоении значения свойству выполняется [[Put]]
операция, и значение присваивается, или свойство создается, а затем присваивается значение. Не все так просто. Существует ряд факторов, определяющих поведение оператора [[Put]]
, в первую очередь, обнаружено свойство у объекта или нет.
Давайте посмотрим, что происходит, когда свойство найдено:
- Если свойство является дескриптором доступа, вызовите установщик (рассматривается далее в этом сообщении в разделе "Получатели и сеттеры").
- Если свойство представляет собой дескриптор данных с записываемым дескриптором, установленным в значение false, автоматически завершается ошибка, если только он не запущен в режиме
strict
. В случае режимаstrict
выдает ошибку. Я рассмотрел дескрипторы данных в своем сообщении Неизменяемость объектов. - В противном случае установите значение свойства как обычно.
Но что происходит, если свойство не найдено на объекте? Вы, возможно, уже догадались, если прочитали, как работает [[Get]]
операция, но это немного больше, чем просто [[Prototype]]
сканирование ссылок, есть и другие правила.
Существуют три (3) правила для определения того, как будет работать операция [[Put]]
, если свойство не найдено в рассматриваемом объекте:
- Если свойство средства доступа к данным находится где-нибудь в
[[Prototype]]
цепочке и его доступный для записи дескриптор (обсуждается здесь) не установлено значение false, тогда Свойство тени создается для рассматриваемого объекта, и значение устанавливается там. - Если свойство средства доступа к данным находится где-нибудь в
[[Prototype]]
цепочке и его доступный для записи дескриптор (обсуждается здесь) установлено значение false, то присвоение автоматически завершится ошибкой, и свойство shadow не будет создано. - Если свойство находится выше в цепочке
[[Prototype]]
и является установщиком, то всегда вызывается функция set. Свойство тени не будет создано для рассматриваемого объекта. При получении значения указанного свойства операция[[Get]]
будет проходить по цепочке[[Prototype]]
и извлекать значение оттуда.
Примечание. теневое свойство - это свойство, которое существует как в самом объекте, так и на более высоких уровнях цепочки [[Prototype]]
. Рассмотрим свойство с именем x
, свойство x
непосредственно на объекте затеняет любое свойство x
, которое может появиться где-нибудь выше в цепочке [[Prototype]]
, потому что поиск obj.x вернет значение непосредственно на объект (самый нижний в цепочке) вместо поиска в цепочке выше. Рассмотрим следующий пример
const obj = { x: 52 }; const mObj = Object.create(obj); // Here's how the following assignment works // Since x isn't on mObj // [[Put]] Traverses the [[Prototype]] chain // Finds the property on obj // Creates a shadow property on mObj // Assigns the shadow property value of 32 mObj.x = 32; // Since x now exists on both mObj and obj // when we do a retrieval of x from mObj // the value 32 will be returned. mObj.x; // 32, because x now exists on mObj obj.x; // 52, nothing changes
Геттеры и сеттеры
К настоящему времени вы должны знать, что операции [[Put]]
и [[Get]]
полностью управляют тем, как значения устанавливаются и извлекаются, а также то, как эти операции фактически работают.
ES5 представил способ переопределения этих функций для каждого свойства с помощью геттеров и сеттеров. Вы можете определить геттеры и сеттеры с помощью метода Object.defineProperty()
. Часть следующего примера покажется вам знакомой, если вы читали мой пост о Неизменяемость объектов.
const obj = { //A getter for property x get x() { return this._x; }, //A setter for property y set y(value) { this._x = value; } }; Object.defineProperty(obj, 'y', { //A getter for y get: function() { return this._y; }, // A setter for y set: function(value) { this._y = value; } }); obj.x; // undefined obj.x = 52; obj.x; // 52 obj.y; // undefined obj.y = 32; obj.y; // 32
В приведенном выше примере мы создаем свойства объекта с помощью
- используя литерал объекта
- используя явное определение
В любом случае мы создаем свойства, которые на самом деле не содержат значения свойства, но вместо этого при доступе они вызывают скрытую функцию для получения или установки значения.
По сути, получатель - это свойство объекта, которое вызывает скрытую функцию для получения значения, а сеттер - это свойство объекта, которое вызывает скрытую функцию для установки значения.
Когда вы определяете геттер или сеттер, либо и то, и другое для свойства, оно становится дескриптором доступа, и вам не нужно определять для них дескрипторы value
или writable
.
Вывод
В этом посте рассказывается, как операции [[Put]]
и [[Get]]
работают для установки или получения значения свойства. Мы рассмотрели различные правила, которые применяются, когда свойство обнаруживается у объекта, а также когда оно не обнаруживается для обеих операций. Мы также рассмотрели, как можно использовать геттеры и сеттеры и как они работают.
Итак, теперь вы должны хорошо понимать, как все это работает. Расскажите мне о своем опыте доступа к объектам недвижимости. Сталкивались ли вы с какими-либо трудностями при его использовании? Сталкивались ли вы с какими-то странностями в коде доступа к ресурсам, которые для вас не имели смысла? Давайте обсудим в комментариях ниже.
Ваше здоровье!