- PVSM.RU - https://www.pvsm.ru -
Доброго времени суток, друзья!
В данной статье мы возьмем функцию из спецификации и разберем ее объяснение. Поехали.
Даже если вы хорошо знаете JavaScript, чтение спецификации [1] может быть затруднительным. Следующий код демонстрирует использование Object.prototype.hasOwnProperty:
const o = {
foo: 1
}
o.hasOwnProperty('foo') // true
o.hasOwnProperty('bar') // false
В примере объект «o» не имеет метода «hasOwnProperty», поэтому мы обращаемся к его прототипу — «Object.prototype» (цепочка прототипов).
Для описания того, как работает Object.hasOwnProperty, в спецификации используется следующий псевдокод:
Object.prototype.hasOwnProperty(V) [2]
Когда hasOwnProperty вызывается с аргументом V, выполняются следующие шаги:
… и
Абстрактная операция HasOwnProperty используется для установления того, имеет ли объект собственное свойство с определенным ключом. Возвращается логическое значение. Операция вызывается с аргументами O и P. Эта операция состоит из следующих этапов:
Что такое «абстрактная операция»? Что такое [[ ]]? Почему перед функцией стоит вопросительный знак? Что означает «утверждение»?
Давайте это выясним.
Начнем с чего-то знакомого. В спецификации встречаются такие значения, как undefined, true и false, которые известны нам по JS. Все они являются «языковыми значениями» (language values) [4], «значениями языковых типов» (values of language types) [5], которые также определяются спецификацией.
Спецификация использует встроенные языковые значения, например, внутренний тип данных может содержать поле со значением true или false. «Движки» JS обычно не используют языковые значения в своих внутренних механизмах. Например, если движок JS написан на C++, он скорее всего будет использовать true и false из C++, а не внутреннее представление булевых значений из JS.
В дополнение к языковым типам, в спецификации используются специальные типы (specification types), представляющие собой типы, которые используются только в спецификации, но не в языке JS. Движки JS не обязаны их выполнять (но могут). В данной статье мы познакомимся со специальным типом «Record» (запись) и его подтипом «Completion Record» (запись о завершении).
Абстрактные операции [6] — это функции, определенные в спецификации; они определяются в целях сокращения спецификации. Движки JS не обязаны выполнять их в качестве самостоятельных функций. В JS их нельзя вызывать напрямую.
Внутренние слоты и внутренние методы [7] обозначаются именами, заключенными в [[ ]].
Внутренние слоты — это элементы (набора) данных объекта JS или специального типа. Они используются для хранения информации о состоянии объекта. Внутренние методы — это функции-члены объекта JS.
Например, каждый объект JS имеет внутренний слот [[Prototype]] и внутренний метод [[GetOwnProperty]].
Внутренние слоты и методы недоступны в JS. Например, мы не можем получить доступ к o.[[Prototype]] или вызвать o.[[GetOwnProperty]](). Движок JS может выполнять их для собственных (внутренних) нужд, но не обязан делать этого.
Иногда внутренние методы становятся одноименными абстрактными операциями, как в случае с [[GetOwnProperty]]:
Когда внутренний метод [[GetOwnProperty]] объекта «O» вызывается с ключом «P», выполняются следующие действия:
OrdinaryGetOwnProperty — это не внутренний метод, поскольку он не связан с каким-либо объектом; объект, с которым он работает, передается ему в качестве параметра.
OrdinaryGetOwnProperty называется «обычным» (ordinary), поскольку он оперирует обычными объектами. Объекты в ECMAScript бывают обычными (ordinary) и необычными (экзотическими, exotic). Объект является обычным, если он ведет себя предсказуемо в ответ на набор методов, называемых основными внутренними методами (essential internal methods). В противном случае (когда объект ведет себя непредсказуемо; не так, как ожидается; когда поведение объекта отклоняется от нормального, является девиантным), он считается необычным.
Самым известным необычным объектом является Array, поскольку его свойство «length» ведет себя нестандартно: установление этого свойства может удалить элементы из массива.
Список основных внутренних методов можно посмотреть здесь [9].
Что насчет вопросительного и восклицательного знаков? Чтобы разобраться с этим, необходимо понять, что такое запись о завершении [10].
Запись о завершении — это специальный тип (определенный исключительно для целей спецификации). Движок JS не обязан иметь аналогичный внутренний тип данных.
Запись о завершении — это тип данных, имеющий фиксированный набор именованных полей. Запись о завершении имеет три поля:
[[Type]] | normal, break, continue, return или throw. Все типы, кроме normal, являются «внезапными (непреднамеренными) завершениями» (abrupt comlpetions) |
[[Value]] | Значение, полученное после завершения, например, значение, которое вернула функция, или выброшенное исключение |
[[Target]] | Используется для прямой управляемой передачи данных (не рассматривается в рамках данной статьи) |
Каждая абстрактная операция неявно возвращает запись о завершении. Даже если результатом абстрактной операции является простое логическое значение, оно оборачивается в запись о завершении с типом normal (см. Implicit Completion Values [11]).
Примечание 1: спецификация не очень последовательна в этой части; существует несколько вспомогательных функций, возвращающих «голые» значения, которые используются как есть, без извлечения из записи о завершении.
Примечание 2: авторы спецификации стремятся сделать обработку записи о завершении более явной.
Если алгоритм выбрасывает исключение, это означает, что будет получена запись о завершении с типом ([[Type]]) throw и значением ([[Value]]) в виде объекта исключения. Мы пока не будем рассматривать другие типы (break, continue и return).
ReturnIfAbrupt(argument) означает выполнение следующих операций:
Вот что из себя представляет запись о завершении; если завершается внезапно, сразу возвращаемся. В противном случае, извлекаем значение из записи о завершении.
ReturnIfAbrupt выглядит как вызов функции, но это не так. Мы вызываем функцию, которая возвращает ReturnIfAbrupt(), а не саму ReturnIfAbrupt. Ее поведение больше похоже на макрос в С-подобных языках программирования.
ReturnIfAbrupt может быть использована следующим образом:
Здесь в игру вступает вопросительный знак [12]: запись ? Foo() эквивалента ReturnIfAbrupt(Foo()). Использование данного сокращения имеет практическую ценность: нам не нужно каждый раз писать код обработчика ошибок.
По аналогии, запись пусть val будет ! Foo() эквивалентна следующему:
Используя эти знания, мы можем переписать Object.prototype.hasOwnProperty следующим образом:
Object.prototype.hasOwnProperty(P)
...HasOwnProperty можно переписать так:
HasOwnProperty(O, P)
Мы также может переписать внутренний метод [[GetOwnProperty]] без восклицательного знака:
O.[[GetOWnProperty]]
Мы предполагаем, что temp — новая временная переменная, которая ни с чем не взаимодействует.
Мы также знаем, что в случае, когда оператор return возвращает нечто, отличное от записи о завершении, это нечто неявно оборачивается в NormalCompletion.
Спецификация использует нотацию Return ? Foo() — почему здесь вопросительный знак?
Запись Return ? Foo() может быть раскрыта следующим образом:
Поведение Return ? Foo() одинаковое как для нормального, так и для внезапного завершения.
Запись Return ? Foo() позволяет более очевидным образом указать, что Foo возвращает запись о завершении.
Утверждения в спецификации «утверждают» инвариантные условия алгоритмов. Они добавлены в спецификацию для ясности, но не содержат никаких требований по реализации, поэтому не нуждаются в проверке со стороны конкретной реализации.
Мы научились читать спецификацию по таким простым методам, как Object.prototype.hasOwnProperty, и таким абстрактным операциям, как HasOwnProperty. Имея эти знания, мы сможем понять, что делают другие абстрактные операции, о которых пойдет речь в следующей части. Также в следующей статье мы рассмотрим дескрипторы свойств (Property Descriptors), которые являются еще одним специальным типом.
Благодарю за внимание. Счастливого кодинга!
Автор: Igor Agapov
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/347920
Ссылки в тексте:
[1] спецификации: https://tc39.es/ecma262/
[2] Object.prototype.hasOwnProperty(V): https://tc39.es/ecma262/#sec-object.prototype.hasownproperty
[3] HasOwnProperty(O, P): https://tc39.es/ecma262/#sec-hasownproperty
[4] «языковыми значениями» (language values): https://tc39.es/ecma262/#sec-ecmascript-language-types
[5] «значениями языковых типов» (values of language types): https://tc39.es/ecma262/#sec-ecmascript-specification-types
[6] Абстрактные операции: https://tc39.es/ecma262/#sec-abstract-operations
[7] Внутренние слоты и внутренние методы: https://tc39.es/ecma262/#sec-object-internal-methods-and-internal-slots
[8] [[GetOwnProperty]](P): https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
[9] здесь: https://tc39.es/ecma262/#table-5
[10] запись о завершении: https://tc39.es/ecma262/#sec-completion-record-specification-type
[11] Implicit Completion Values: https://tc39.es/ecma262/#sec-implicit-completion-values
[12] вопросительный знак: https://tc39.es/ecma262/#sec-returnifabrupt-shorthands
[13] Источник: https://habr.com/ru/post/490084/?utm_source=habrahabr&utm_medium=rss&utm_campaign=490084
Нажмите здесь для печати.