Русский
Русский
English
Статистика
Реклама

Из песочницы Понимание спецификации ECMAScript, часть 2

Привет, Хабр! Представляю вашему вниманию перевод статьи под редакцией xfides


Автор оригинала: Marja Hltt
Перевод первой части.



Давайте еще попрактикуемся вчтении спецификации. Если выневидели предыдущую статью, самое время сходить еепосмотреть. Впервой части мыпознакомились спростым методом Object.prototype.hasOwnProperty. Также, посмотрели список абстрактных операций, которые вызываются при выполнении этого метода. Еще мыузнали оспецифических сокращениях ? и !, которые связаны собработкой ошибок. Инаконец, мыполучили информацию отипах языка, типах спецификации, внутренних слотах ивнутренних методах.


Готовы для второй части?


Предупреждение! Этот эпизод содержит копию алгоритмов изспецификации ECMAScript отфевраля 2020года. Естественно, современем информация будет устаревать.


Мызнаем, что свойства ищутся попрототипной цепочке: если объект неимеет свойства, которое пытаемся прочитать, мыбудем подниматься вверх попрототипной цепочке, пока ненайдем его, или пока ненайдем объект, укоторого небудет своего прототипа. Например:


const o1 = { foo: 99 };const o2 = {};Object.setPrototypeOf(o2, o1);o2.foo;//  99

Где определяется алгоритм хождения попрототипной цепочке?


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


Есть два метода [[GetOwnProperty]] и [[Get]]. Нам интересен тот, который умеет работать спрототипной цепочкой это метод [[Get]]. Ксожалению, дескриптор свойства вспецификации также имеет поле под названием [[Get]], поэтому, читая спецификацию, необходимо различать, когда речь идет одескрипторе или обалгоритме.


[[Get]] это базовый внутренний метод. Категория Обычных объектов реализуют поведение (алгоритм использования объекта) поумолчанию для базовых внутренних методов. Категория Экзотичных объектов могут определять свой собственный внутренний метод [[Get]], логика которого отличается отповедения поумолчанию. Вэтом посте мысфокусируемся наобычных объектах.


Метод обычного объекта [[Get]] ( P, Receiver ). вызывает OrdinaryGet. При этом, когда вызывается внутренний метод [[Get]] отобъекта О сосвойством Р иECMAScript значением Receiver, выполняется следующий шаг:


    1. Return ? OrdinaryGet(O, P, Receiver).

Вскоре мыувидим, что Receiver это значение, которое используется как this значение, когда вызывается геттер функция свойства-акцессора.


OrdinaryGet(O, P, Receiver) определен следующим образом:


1.  Assert: IsPropertyKey(P) is true.2.  Let desc be ? O.[[GetOwnProperty]](P).3.  If desc is undefined, then        a. Let parent be ? O.[[GetPrototypeOf]]().        b. If parent is null, return undefined.        c. Return ? parent.[[Get]](P, Receiver).4.  If IsDataDescriptor(desc) is true, return desc.[[Value]].5.  Assert: IsAccessorDescriptor(desc) is true.6.  Let getter be desc.[[Get]].7.  If getter is undefined, return undefined.8.  Return ? Call(getter, Receiver).

Прототипная цепочка идет внутри третьего шага: если мыненашли собственное свойство, мывызываем прототипный [[Get]] метод, который снова вызывает OrdinaryGet. Если мывсе еще ненашли свойство, мывновь вызываем прототипный [[Get]] метод, который опятьже передает вызов OrdinaryGet. Так продолжается дотех пор, пока мыненайдем свойство, или недостигнем объекта спрототипом равным null.


Давайте посмотрим, как этот алгоритм работает, когда мыобращаемся к o2.foo. Сначала мывызываем OrdinaryGet ипередаем вкачестве параметра О объект о2, авкачестве параметра Р имя свойства foo. После того, как O.[[GetOwnProperty]](foo) возвращает undefined, мыидем вконструкцию ifвшаге3, поскольку объект o2 неимеет собственного свойства под именем foo.


Вшаге 3.a, мывозвращаем впеременную parent ссылку напрототип объекта o2 это объект o1. Так как parent не null, мынепроходим проверку ifнашаге 3.b.


Вшаге 3.с мывызываем родительский метод [[Get]] сназванием свойства foo ивозвращаем его результат. Так как родительский объект o1 обычный объект, тометод [[Get]] вызывает OrdinaryGet снова. Вэтом случае, вкачестве параметра О попадает о1, авР передается foo.


Нашаге 2метод O.[[GetOwnProperty]](foo) возвращает ассоциированный сосвойством дескриптор, имысохраняем его впеременную desc.


Дескриптор свойства это тип спецификации. Дескрипторы данных хранят значения свойства непосредственно вполе [[Value]]. Дескриптор акцессор хранит функции акцессоры вполе [[Get]] и/или [[Set]]. Внашем случае, дескриптор свойства ассоциируемый сfoo это дескриптор данных.


Как выпомните, дескриптор свойства мысохранили в desc нашаге2, поэтому мынепроходим проверку ifнашаге 3.


Далее мывыполняем шаг 4. Дескриптор свойства представлен дескриптором данных, поэтому вернется значение99, которое лежит вполе [[Value]] нашаге 4. Инаэтом закончится алгоритм.


Что такое Receiver иоткуда онвзялся?


Параметр Receiver используется только вслучае свойств-акцессора вшаге 8. Онпередает значение this, когда вызывается геттер функция свойства-акцессора.


OrdinaryGet передает оригинальный Receiver через рекурсию без изменений (шаг 3.c). Давайте выясним, откуда взялся оригинальный Receiver.


Наместе, где вызывается [[Get]], будет абстрактная операция GetValue, которая работает сReference. Reference это тип спецификации, содержащий базовое значение, указанное имя ифлаг strict. Внашем случае с o2.foo базовое значение будет объект o2, указанное имя строка foo, афлаг strict false.


Отступление: почему тип Reference неявляется типом Record?


Тип Reference неявляется типом Record, как это моглобы быть. Онтакже содержит три компонента, каждый изкоторых может быть выражен через именованное поле. Новсеже, тип Reference нетип Record только поисторическим причинам.


Вернемся кGetValue.


Давайте посмотрим, как алгоритм GetValue ( V ) описан:


1.  ReturnIfAbrupt(V).2.  If Type(V) is not Reference, return V.3.  Let base be GetBase(V).4.  If IsUnresolvableReference(V) is true, throw a ReferenceError exception.5.  If IsPropertyReference(V) is true, then     а.If HasPrimitiveBase(V) is true, then         i.Assert: In this case, base will never be undefined or null.         ii.Set base to ! ToObject(base).     b.Return ? base.[[Get]](GetReferencedName(V),   GetThisValue(V)).6.  Else,      a.Assert: base is an Environment Record.      b.Return ? base.GetBindingValue(GetReferencedName(V), IsStrictReference(V))

Reference внашем примере это o2.foo, которое является property reference.


Итак, мызаходим вifнашаге 5. Непроходим проверку нашаге 5.a, поскольку объект о2 неявляется примитивом (число, строка, символ, BigInt, Boolean, Undefined, или Null).


Затем мывызываем [[Get]] нашаге 5.b. Receiver, который мыпередаем это значение, полученное после абстрактной операции GetThisValue(V). Внашем случае GetThisValue( V ) вернет значение базы Reference:


1.  Assert: IsPropertyReference(V) is true.2.  If IsSuperReference(V) is true, then        a.Return the value of the thisValue component of the reference V.3.  Return GetBase(V).

Для нашего примера o2.foo, мынепроходим ifнашаге2, поскольку o2.foo неявляется Super Reference(как например super.foo), однако, выполняем шаг 3ивозвращаем значение базы Reference, которым является объект o2.


Объединив все вместе, мыобнаружим, что мызадаем Receiver быть значением базы для оригинальной Reference, и, таким образом, мысохраняем его неизменным вовремя прохождения попрототипной цепочки. И, витоге, если свойство, которое мынашли, является свойством-акцессором, мыиспользуем Receiver как значение this.


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


Давайте опробуем!


const o1 = { x: 10, get foo() { return this.x; } };const o2 = { x: 50 };Object.setPrototypeOf(o2, o1);o2.foo;//  50

Вэтом примере унас есть свойство-акцсессор под именем foo, имыопределяем геттер для него. Геттер возвращает this.x..


Затем мыпытаемся получить значение o2.foo что вернет геттер?


Мыобнаружили, что, когда мывызываем геттер, значение this является объект, изкоторого мыизначально пытались получить свойство, анеобъект, вкотором мыего нашли. Внашем случае, значением this будет объект о2, анеобъект о1. Вэтом мыможем удостоверится, проверив, что возвращает геттер: o2.x или o1.x. Вдействительности онвозвращает o2.x.


Да, это работает! Мысмогли спрогнозировать поведение небольшого кусочка кода наосновании того, что мыпрочитали вспецификации.


Свойства акцессоры почему они вызывают [[Get]]?


Где вспецификации сказано, что внутренний метод объекта [[Get]] будет вызывается, когда получит доступ ксвойству o2.foo? Конечно, это должно быть где-то описано. Неверьте мне наслово!


Мынашли, что внутренний метод объекта [[Get]] вызвался изабстрактной операции GetValue, которая работает сReferences. Нооткуда вызывается GetValue?


Семантика вовремя выполнения для MemberExpression


Грамматические правила спецификации определяют синтаксис языка. Семантика вовремя выполнения определяет смысл синтаксических конструкции, ито, как они будут выполнятся.


Если выдосих пор незнакомыс контекстно-свободной грамматикой, самое время познакомится снее сейчас.


Мыбудем более глубоко разбирать грамматические правила впоследующей главе, апока давайте сохраним простоту. Вчастности, мыможем игнорировать индексы (Yield, Await ит.д.) впродукциях внашем примере.


Следующие продукции описывают, как выглядит MemberExpression :


MemberExpression :     PrimaryExpression     MemberExpression [ Expression ]     MemberExpression . IdentifierName     MemberExpression TemplateLiteral     SuperProperty     MetaProperty     new MemberExpression Arguments

Здесь представлены 7продукций для MemberExpression.


MemberExpression может быть просто PrimaryExpression. Также MemberExpression может состоять издругого MemberExpression и Expression, соединенных вместе: MemberExpression[Expression], например o2[foo]. Или онможет быть MemberExpression.IdentifierName, например o2.foo это представление подходит для нашего примера.


Семантика вовремя выполнения для продукции Runtime Semantics: Evaluation for MemberExpression: MemberExpression. IdentifierName имеет следующим алгоритм:


1.  Let baseReference be the result of evaluating MemberExpression.2.  Let baseValue be ? GetValue(baseReference).3.  If the code matched by this MemberExpression is strict mode code, let strict be true; else let strict be false.4.  Return ? EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict).

Алгоритм переходит кабстрактной операции EvaluatePropertyAccessWithIdentifierKey, поэтому нам также необходимо еепрочитать. Абстрактная операция EvaluatePropertyAccessWithIdentifierKey(baseValue, identifierName, strict) принимает вкачестве аргументов значение baseValue, identifierName, атакже strict ивыполняет следующий алгоритм:


1.  Assert: identifierName is an IdentifierName2.  Let bv be ? RequireObjectCoercible(baseValue).3.  Let propertyNameString be StringValue of identifierName.4.  Return a value of type Reference whose base value component is bv, whose referenced name component is propertyNameString, and whose strict reference flag is strict.

Таким образом, EvaluatePropertyAccessWithIdentifierKey создает Reference, который использует предоставленный baseValue вкачестве base, строковое значение identifierName как имя свойства, иstrict как флаг строго режима.


Витоге, Reference передается в GetValue. Это прописано внескольких местах вспецификации, взависимости оттого, как Reference вконечном итоге будет использован.


MemberExpression как параметр


Внашем примере мыиспользуем свойство доступа как параметр:


console.log(o2.foo);

Вэтом случае, используется продукция ArgumentList: AssignmentExpression. Дальше рассматривается ееповедение, определенное валгоритме семантики для этой продукции. Внем вызывается GetValue для аргумента:


Runtime Semantics: ArgumentListEvaluation


1.  Let ref be the result of evaluating AssignmentExpression.2.  Let arg be ? GetValue(ref).3.  Return a List whose sole item is arg.

o2.foo невыглядит как AssignmentExpression, ноэто так иесть, поэтому применяем эту продукцию. Узнать, почему так, выможете, прочитав эту дополнительную статью, новэтом нет острой необходимости сейчас.


Вшаге 1происходит вычисления алгоритма AssignmentExpression, которым является o2.foo. Вref попадет результат вычисления.


Вшаге 2мы вызываем GetValue отнего. Таким образом, мызнаем, что внутренний метод объекта [[Get]] будет вызван, иобход попрототипной цепочки произойдет.


Резюме


Вэтой части мырассмотрели, как спецификация определяет особенности языка; внашем случае-прототипный поиск через все различные слои: синтаксические конструкции, что запускают алгоритмы ишаги, определяющиеих.

Источник: habr.com
К списку статей
Опубликовано: 02.10.2020 16:05:32
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка веб-сайтов

Javascript

Программирование

Ecmascript

Specification

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru