Как доступ к собственности осуществляется под капотом

Примечание. Этот пост является несколько сложным и предполагает, что вы имеете некоторое представление о том, что такое [[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. Я знаю, это может сбить с толку, если вы не знакомы с этим, но обещаю, я расскажу об этом в одной из следующих публикаций.

Давайте разберемся с этим и рассмотрим несколько примеров, чтобы увидеть, как это работает.

  1. [[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]], в первую очередь, обнаружено свойство у объекта или нет.

Давайте посмотрим, что происходит, когда свойство найдено:

  1. Если свойство является дескриптором доступа, вызовите установщик (рассматривается далее в этом сообщении в разделе "Получатели и сеттеры").
  2. Если свойство представляет собой дескриптор данных с записываемым дескриптором, установленным в значение false, автоматически завершается ошибка, если только он не запущен в режиме strict. В случае режима strict выдает ошибку. Я рассмотрел дескрипторы данных в своем сообщении Неизменяемость объектов.
  3. В противном случае установите значение свойства как обычно.

Но что происходит, если свойство не найдено на объекте? Вы, возможно, уже догадались, если прочитали, как работает [[Get]] операция, но это немного больше, чем просто [[Prototype]] сканирование ссылок, есть и другие правила.

Существуют три (3) правила для определения того, как будет работать операция [[Put]], если свойство не найдено в рассматриваемом объекте:

  1. Если свойство средства доступа к данным находится где-нибудь в [[Prototype]] цепочке и его доступный для записи дескриптор (обсуждается здесь) не установлено значение false, тогда Свойство тени создается для рассматриваемого объекта, и значение устанавливается там.
  2. Если свойство средства доступа к данным находится где-нибудь в [[Prototype]] цепочке и его доступный для записи дескриптор (обсуждается здесь) установлено значение false, то присвоение автоматически завершится ошибкой, и свойство shadow не будет создано.
  3. Если свойство находится выше в цепочке [[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]] работают для установки или получения значения свойства. Мы рассмотрели различные правила, которые применяются, когда свойство обнаруживается у объекта, а также когда оно не обнаруживается для обеих операций. Мы также рассмотрели, как можно использовать геттеры и сеттеры и как они работают.

Итак, теперь вы должны хорошо понимать, как все это работает. Расскажите мне о своем опыте доступа к объектам недвижимости. Сталкивались ли вы с какими-либо трудностями при его использовании? Сталкивались ли вы с какими-то странностями в коде доступа к ресурсам, которые для вас не имели смысла? Давайте обсудим в комментариях ниже.

Ваше здоровье!