Вопрос: Почему мы должны применить деструктуризацию параметра чтобы получить массив который передаем при создании.
Код:
const trapsObject = {
construct(target, [args]){
console.log(args); /// [1,2,3]
return {};
}
};
const Exo = new Proxy(Array, trapsObject);
new Exo([1,2,3]);
TL;DR
Внутри объекта proxy
, внутри метода [[Construct]]
есть операция CreateArrayFromList, она упаковывает все аргументы в массив. То есть передали что то такое: ([1,2,3])
получили [[1,2,3]]
или ([1,2,3],"text")
получили [[1,2,3],"text"]
.
Пример реализации (не является реальной реализацией или полифилом):
function createArrayFromList(...elements){
const array = [];
let n = 0;
for(const e of elements){
Object.defineProperty(array, String(n), {
value: e,
writable: true,
enumarable: true,
configurable: true
});
n++;
}
return array;
}
Поэтому используется деструктуризация на уровне формальных параметров. Но а для особо преисполненных постижением меча и магии ECMAScript добро пожаловать вниз :)
Глава 1: Оружие и ножны
Для того чтобы раскрыть ваш вопрос нужно вообще понять что такое формальные параметры и почему они отличаются от аргументов. Большинство программистов вообще не отдают отчета этому и говорят что это одно и тоже. Но это не так. Аргумент это то что передается функции при непосредственном ее вызове, например так myFunc("hello")
, "hello" - это аргумент. Параметр это номенклатура функции, она показывает в какие именно переменные будут помещены значения, а также будут ли они инициализированы значениями по умолчанию. Например function myFunc(text){}
, text - это параметр, в функции мы его потом сможем использовать, в отличии от аргумента, который имеет просто значение и к которому нельзя обратиться из кода. На этом разница между аргументами и параметрами завершена.
Теперь представим что у нас есть объект-функция, которая вызывается, например:
function myFunc(a, b, c){
console.log(a, b, c);
}
myFunc(1,2,3);
Этап 1: Как разбиваются формальные параметры на производства / определение производств формальных параметров.
Если разбить определение функции на производства то получится следующее: function BindingIdentifier(FormalParameters){FunctionBody}
Из него мы вытаскиваем его подчасть и это FormalParameters
что соответствует нашему (1,2,3)
выше. Как же (a,b,c)
разбивается на более мелкие части? Давайте просто посмотрим как дробится производство FormalParameters
:
FormalParameters: [empty] FunctionRestParameter FormalParameterList FormalParameterList, FormalParameterList, FunctionRestParameter
Мы видим что [empty]
показывает что бывает что параметров у функции иногда не бывает и это так. Наш случай это FormalParameterList
рассмотрим его дробление:
FormalParameterList: FormalParameter FormalParameterList, FormalParameter
Вариативность чуть уменьшилась, и такой шаблон как вы можете заметить указывает на рекурсию, так как он указывает на самого себя во втором пункте, это говорит о том что количество параметров на данном этапе может быть больше чем один.
Хорошо давайте посмотрим как наши параметры соотвтествуют текущему производству:
FormalParameterList
полное соответсвие к (a,b,c)
FormalParameterList, FormalParameter
тут уже вот так (a,b),(c)
FormalParameter, FormalParameter, FormalParameter
а тут так (a),(b),(c)
Теперь смотрим на что бьется FormalParameter
.
FormalParameter: BindingElement
Выглядит так словно мы у цели, хорошо, смотрим что из себя представляет BindingElement
:
BindingElement: SingleNameBinding BindingPattern Initializer opt
Видим что это еще не конец, ну хорошо, тут я уже знаю в какую ветку мы идем это в SingleNameBinding
(другая ветвь приведет нас к деструктуризации, нам пока не надо у нас упрощенный пример):
SingleNameBinding: BindingIdentifier Initializer opt
Тут уже видим что мы почти что совсем достигли самого низа, в примечание скажу Initializer
у нас отсутствует так как у нас номенклатура параметров такая (a,b,c)
, а не (a = 1, b = 2, c = 3)
, об этом говорит маркировка opt (optional - необязательный). Идем ниже в BindingIdentifier
:
BindingIdentifier: Identifier
И еще:
Identifier: IdentifierName but not ReservedWord
Все, дальше это уровень токенов, частей языка когда происходит анализ каждого символа, чтобы понять что это за строительный элемент получается, ссылка на это вот
Хорошо теперь давайте возьмем ваш пример и представим что FormalParameters
это (target, [args])
, в таком случае идет такая вереница:
FormalParameters: [empty] FunctionRestParameter FormalParameterList FormalParameterList, FormalParameterList, FunctionRestParameter
Что в свою очередь является FormalParameterList
:
FormalParameterList: FormalParameter FormalParameterList, FormalParameter
Что также является FormalParameter
:
FormalParameter: BindingElement
Где BindingElement
:
BindingElement: SingleNameBinding BindingPattern Initializer opt
Вот тут стоит сделать пояснение, наша конструкция (target, [args])
в соответствии с этим производством представляется как (SingleNameBinding, BindingPattern)
, как видите это два разных производства если подниматься выше они перерастут в (BindingElement, BindingElement)
что в свою очередь это будет (FormalParameter, FormalParameter)
, чем также это будет являться (FormalParameterList)
. Надеюсь этот шаг разъясняет логику вложения. Но мы идем дальше, теперь имейте ввиду что первый парметр - простой, мы его раскладывать не будем, я его оставлю таким каким он есть на этом шаге, а вот параметр [args]
то есть BindingPattern
я раскрою так как сделал с SingleNameBinding
ранее:
BindingPattern: ObjectBindingPattern ArrayBindingPattern
Далее так как у нас ([args])
то выбираем ArrayBindingPattern
:
ArrayBindingPattern: [Elision opt BindingRestElement opt] [BindingElementList] [BindingElementList, Elision opt BindingRestElement opt]
Теперь выражение (target, [args])
выглядит так (SingleNameBinding, [BindingElementList])
, рассмотрим теперь как раскладывается [BindingElementList]
:
BindingElementList: BindingElisionElement BindingElementList, BindingElisionElement
Заметим что это опять рекурсия как и в случае с FormalParameterList
, это много где встречается когда нужна повторяющаяся запись. Наш случай не рекурсивен так как имеет один элемент и это BindingElisionElement
:
BindingElisionElement: Elision opt BindingElement
Теперь посмотрим BindingElement
:
BindingElement: SingleNameBinding BindingPattern Initializer opt
И мы видим как данное производство нам намекает что деструктуризация может быть вложенной то есть вместо [args]
может быть такое [[[[[args]]]]]
. Но у нас один уровень вложенности и мы его уже раскрыли, поэтому следующее раскрытие будет с производства SingleNameBinding
которое мы рассмотрели ранее.
Ну и (a), (b), (c)
это все по отдельности IdentifierName
, которые в свою очередь могут быть производствами по-старше как я показывал ранее.
Было бы неплохо сделать рисунок всего этого, но я думаю вы сможете сделать это сами на основании предоставленной информации
Резюме этого этапа: так происходит разбиение на маленькие кирпичики любой синтаксической конструкции. Но раскрывать такие вещи нужно учиться по главе Notational Conventions. Только в этом объяснении я решил сделать исключение.
Этап 2: Выполнение производств формальных параметров.
После того как мы рассмотрели производства теперь стоит узнать, что и как вообще будет выполняться. Мы не будем углубляться в цепочку определения функции и ее вызова, мы сразу отправимся в момент когда происходит этап преинициализации вызова функции то есть этап, который создает служебные штуки для вызова функции и поддержания ее работоспособности (более подробнее можно почитать в спецификации, в преамбуле полный смысл) эта операция называется FunctionDeclarationInstantiation в нее передаются функция, которую вызывают и список аргументов List
(List
это специальный тип спецификации нельзя его приравнивать к списку аргументов в js; узнать подробнее об этом типе можно тут).
Теперь мы в алгоритме преинициализации, он невероятно большой как правило такие алгоритмы аккумулируют в себе очень большое количество концепций и этот не стал исключением, но так как наша цель посмотреть как реагируют формальные параметры мы будем сосредоточены на шагах это действо производящее.
Итак я буду указывать конкретные этапы шагов и пояснять:
4) Let formals be func.[[FormalParameters]].
Достает из внутреннего поля вызываемой функции Parse Node
узел с производствами формальных параметров и помещает в соответствующую переменную.
5) Let parameterNames be the BoundNames of formals.
Переменной parameterNames
назначает привязанные имена переменных получающиеся при выполнении вычисления BoundNames
над переменой formals
. Возвращает спецификационный тип List
.
6) If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false.
Если переменная parameterNames
имеет дубликаты записей, тогда hasDuplicates
присвоить true
, в противном случае hasDuplicates
должен быть false
.
7) Let simpleParameterList be IsSimpleParameterList of formals.
Переменной simpleParameterList
присваивается результат выполнения IsSimpleParameterList
над formals
переменной. Проверка происходит над тем является ли производство формальных параметров простым списком. Если simpleParameterList
простой тогда результатом будет true
, в противном случае false
.
8) Let hasParameterExpressions be ContainsExpression of formals.
Переменной hasParameterExpressions
присваивается результат выполнения ContainsExpression
над formals
переменной. Проверка происходит на то являются ли они сложными. Если меня не подводит память критерий сложности определяется по инициализатору. То есть (a = 100, b, c)
, такой список параметров является сложным. Возврат значения true/false
.
17) Else if "arguments" is an element of parameterNames, then a. Set argumentsObjectNeeded to false.
Если "arguments"
- это элемент из parameterNames
, тогда установить argumentsObjectNeeded
в false
. Так как шаг создания экзотического объекта arguments
рассказывать не буду (я и не разбирал в деталях его внутренности), скажу так этот шаг делает определение на то существует ли у нас параметр с именем соответствующим имени экзотическому объекту. И если он совпадает то конструирование экзотического объекта в дальнейшем отпадает. Проверить это можно легко - создайте объект-функцию определите параметр в ней с именем "arguments"
и сделайте вызов этой функции с параметром. Вы увидите что когда вы вызываете такую функцию внутри нее если есть обращение к "arguments"
- будет ваш аргумент, а не экзотический объект создаваемый реализацией языка.
21) For each String paramName of parameterNames, do a. Let alreadyDeclared be env.HasBinding(paramName). b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict functions that do not have parameter default values or rest parameters. c. If alreadyDeclared is false, then i. Perform ! env.CreateMutableBinding(paramName, false). ii. If hasDuplicates is true, then 1. Perform ! env.InitializeBinding(paramName, undefined).
Пояснение в виде алгоритма:
Для каждого строкового значения paramName из parameterNames сделать: a. Пусть alreadyDeclared будет env.HasBinding(paramName), где env - текущий Environment Record для данного вызова, а HasBinding это метод-операция этого окружения. Все это вызывает проверку на существование конкретного имени, в нашем окружении. Возвращает true/false b. Примечание: Early Errors гарантируют что дублирующие имена параметров могут происходить только в нестрогих функциях которые не имеют значений по умолчанию или остаточных параметров. c. Если alreadyDeclared - false, тогда i. Выполнить env.CreateMutableBinding(paramName, false), где env - текущий Environment Record для данного вызова, а CreateMutableBinding это метод-операция этого окружения. Смысл операции создать изменяемую привязку, то есть переменную которая сможет менять значения в течении выполнения нашей программы. ii. Если hasDuplicates - true, тогда 1. Выполнить env.InitializeBinding(paramName, undefined), где env - текущий Environment Record для данного вызова, а InitializeBinding это метод-операция этого окружения. Смысл состоит в том чтобы задать начальное значение нашему очередному привязанному имени.
Надеюсь перевод алгоритма с небольшими ремарками вносит понимание.
24) Let iteratorRecord be CreateListIteratorRecord(argumentsList).
Пусть переменной iteratorRecord
будет задан результат выполнения операции CreateListIteratorRecord
c переданным в него аргументом argumentList
.
Так как операция имеет вес в объяснении мне придется ее разложить прямо здесь же; переходим в операцию CreateListIteratorRecord
Примечательный факт: в спецификации только единственное упоминание о ней и это в нашем крупном алгоритме преинициализации вызова функции.
Данная функция принимает спецификационный список (List
), поэтому эта операция предназначена для того чтобы понятным для имплементации языком показать как ей понимать аргументы перечисленные через запятую. Также эта операция создает запись итератора, чей метод "next"
возвращает последовательно элементы списка переданные в качестве спецификационного List
.
Итак алгоритм рассмотрим полностью:
1. Let closure be a new Abstract Closure with no parameters that captures list and performs the following steps when called: a. For each element E of list, do i. Perform ? Yield(E). b. Return undefined. 2. Let iterator be ! CreateIteratorFromClosure(closure, empty, %IteratorPrototype%). 3. Return Record { [[Iterator]]: iterator, [[NextMethod]]: %GeneratorFunction.prototype.prototype.next%, [[Done]]: false }. NOTE The list iterator object is never directly accessible to ECMAScript code.
Пояснение:
1. Пусть closure будет новым созданным типом Abstract Closure (это спецификационный тип замыкания) у которого нет параметров, но которое захватывает переменую list, данное замыкание выполняется когда вызывается: a. Для каждого элемента E из list, сделать i. Выполнить Yield(E) b. Вернуть undefined 2. Пусть iterator переменная будет равна результату выполнения CreateIteratorFromClosure(closure, empty, %IteratorPrototype%). 3. Вернуть Record {[[Iterator]]:iterator, [[NextMethod]]:%GeneratorFunction.prototype.prototype.next%, [[Done]]:false} Примечание: Список объекта-итератора никогда недоступен напрямую из кода ECMAScript. То есть никакими средствами вы не можете получить результат этой операции.
Хорошо думаю что нужно пояснить отдельные моменты этого алгоритма.
- Шаг 1 является замыканием, и он не исполняется в тот момент когда мы читаем алгоритм, для программы которая исполняет эти шаги, этот шаг является просто кодом функции, которая сохраняется в переменную.
- Шаг 2 вызывает CreateIteratorFromClosure что приводит нас к созданию итератора, по совместительству эта операция является собственностью механизма генераторов. Стоит сказать, что эта операция встречается у большинства объектов которые могут быть преобразованы в обычный массив с помощью
rest
синтаксиса. Сейчас всвязи с этим придетя погрузиться в механику итераторов и генераторов. Генераторные функции не используют этот алгоритм.
Этап 2.1: Итераторы-генераторы.
Так как CreateIteratorFromClosure является ключевым алгоритмом в операциях связанных с разложением аргументов и структур на массивы первого уровня (одномерные), мы его рассмотрим на предмет ключевых основ и концепций. Я возьму часть шагов для объяснения, но алгоритм посмотрим весь.
Алгоритм:
1. NOTE: closure can contain uses of the Yield shorthand to yield an IteratorResult object. 2. Let internalSlotsList be « [[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]] ». 3. Let generator be ! OrdinaryObjectCreate(generatorPrototype, internalSlotsList). 4. Set generator.[[GeneratorBrand]] to generatorBrand. 5. Set generator.[[GeneratorState]] to undefined. 6. Let callerContext be the running execution context. 7. Let calleeContext be a new execution context. 8. Set the Function of calleeContext to null. 9. Set the Realm of calleeContext to the current Realm Record. 10. Set the ScriptOrModule of calleeContext to callerContext's ScriptOrModule. 11. If callerContext is not already suspended, suspend callerContext. 12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context. 13. Perform ! GeneratorStart(generator, closure). 14. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. 15. Return generator.
Перевод:
1. Примечание: параметр closure может содержать использования Yield стенографий(сокращения), чтобы получить объект IteratorResult. 2. Пусть internalSlotsList будет списком (List): « [[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]] » 3. Пусть generator будет результатом вызова OrdinaryObjectCreate(generatorPrototype, internalSlotsList). 4. Установить generator.[[GeneratorBrand]] значение из generatorBrand 5. Установить generator.[[GeneratorState]] значение undefined 6. Пусть callerContext будет текущим (работающим в данный момент) исполнительным контекстом 7. Пусть calleeContext будет новым новым исполнительным контекстом (создается новый в прямом смысле слова). 8. Установить компонент Function принадлежащий calleeContext в значение null. 9. Установить компонент Realm принадлежащий calleeContext в значение текущей Realm Record. 10. Установить компонент ScriptOrModule принадлежащий calleeContext в значение которое имеет компонент ScriptOrModule принадлежащий callerContext 11. Если callerContext еще не приостановлен, присотановить callerContext 12. Положить в стек исполнительных контекстов calleeContext; calleeContext сейчас является запущенным исполнительным контекстом. 13. Выполнить операцию GeneratorStart(generator, closure) 14. Удалить calleeContext из стека исполнительных контекстов и востановить callerContext как запущенный исполнительный контекст. 15. Вернуть значение из generator
Итак резюме этого алгоритма:
- Создается объект в переменную generator, этот generator имеет специальные поля
[[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]]
.[[GeneratorState]]
- отвечает за состояние генератора.[[GeneratorContext]]
- отвечает за контекст генератора.[[GeneratorBrand]]
- отвечает за производителя генератора. Таблица этих полей тут - Можем заметить что на протяжении этого алгоритма меняются исполнительные контексты и это удивительно, потому что по факту мы не создаем вызовов дополнительных вызовов функций, которые могли бы породить эти контексты. Но факт есть факт, для переданного списка аргументов операция начинает производить контексты.
- Шаг 13 вызывает функцию
GeneratorStart
, которая является базисом вызова всех генераторных функций; позже рассмотрим. - Шаг 15 возвращает специальный генератор, который будет использован для инициализации наших параметров функции.
Этап 2.1.1: Вызов генераторов.
Как было обещано выше (шаг 13) рассмотрим алгоритм GeneratorStart это классика, выполняется у всех генераторных функций, также используется в нашем алгоритме. Как вы можете заметить передается объект, который является generator
с его внутренними полями переменной и Abstract Closure
в качестве исполняемого кода.
В качестве примера такой код:
function *a(){}
a();
Так вот вызов генераторной функции также производит вызов GeneratorStart
.
Итак алгоритм:
1. Assert: The value of generator.[[GeneratorState]] is undefined. 2. Let genContext be the running execution context. 3. Set the Generator component of genContext to generator. 4. Set the code evaluation state of genContext such that when evaluation is resumed for that execution context the following steps will be performed: a. If generatorBody is a Parse Node, then i. Let result be the result of evaluating generatorBody. b. Else, i. Assert: generatorBody is an Abstract Closure with no parameters. ii. Let result be generatorBody(). c. Assert: If we return here, the generator either threw an exception or performed either an implicit or explicit return. d. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. e. Set generator.[[GeneratorState]] to completed. f. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point. g. If result.[[Type]] is normal, let resultValue be undefined. h. Else if result.[[Type]] is return, let resultValue be result.[[Value]]. i. Else, i. Assert: result.[[Type]] is throw. ii. Return Completion(result). j. Return CreateIterResultObject(resultValue, true). 5. Set generator.[[GeneratorContext]] to genContext. 6. Set generator.[[GeneratorState]] to suspendedStart. 7. Return NormalCompletion(undefined).
Перевод:
1. Утверждаю: Значение generator.[[GeneratorState]] является undefined 2. Пусть genContext это запущенный исполнительный контекст 3. Установить компонент Generator принадлежащий genContext в значение из generator 4. Установить состояние оцениваемого кода которое принадлежит genContext таким чтобы когда оценка продолжилась для этого исполнительного контекста следующие шаги были выполнены: a. Если generatorBody это Parse Node, тогда i. Пусть result будет результатом оценивания generatorBody b. Иначе, i. Утверждаю: generatorBody это Abstract Closure без параметров. ii. Пусть result будет generatorBody() c. Утверждаю: Если мы возвращаемся сюда, генератор либо выбросил исключение либо выполнил невяный или явный возврат. d. Удалить genContext из стека исполнительных контекстов и восстановить исполнительный контекст как запущенный контекст, который находится на верхушке стека исполнительных контекстов. e. Установить generator.[[GeneratorState]] в значение completed. f. Как только генератор входит в статус completed он никогда не покидает его и его ассоциированный исполнительный контекст никогда не продолжается. Любое состояние выполнения ассоциированное с generator может быть отклонено на этом этапе. g. Если result.[[Type]] это значение normal, пусть resultValue будет значением undefined h. Иначе если result.[[Type]] это значение return, пусть resultValue будет значением из result.[[Value]] i. Иначе, i. Утверждаю: result.[[Type]] это значение throw ii. Вернуть Completion(result) j. Вернуть CreateIterResultObject(resultValue, true) 5. Установить generator.[[GeneratorContext]] в значение из genContext 6. Установить generator.[[GeneratorState]] в значение suspendedStart 7. Вернуть NormalCompletion(undefined)
Резюме алгоритма:
- Алгоритм отвечает за первичный вызов генератора. Следовательно также играет контекстами.
- Большая часть алгоритма это
Abstract Closure
, которая замыкаетgenContext
чтобы знать состояние оценки исполняемого кода. - Устанавливает специальные значения в объект нашего генератора и возвращает ничего. Рассказывать про
Completion
записи не буду почитать можно тут
Этап 2: Продолжение
На этом шаг 13 в алгоритме CreateIteratorFromClosure
разобран.
Возвращаемся к CreateListIteratorRecord
, я не сказал что третий шаг возвращает специальный тип данных. Так вот он возвращает Record
c полями: [[Iterator]], [[NextMethod]], [[Done]]
. [[Iterator]]
- имеет значение предыдущего шага, это результат вызова функции порождающий генератор. [[NextMethod]]
- содержит метод next
. [[Done]]
- имеет значение false
.
На этом заканчивается данная операция.
Возвращаемся к алгоритму FunctionDeclarationInstantiation
Сейчас самый интересный этап, который будет заниматься скрещиванием формальных параметров с фактическими аргументами.
Привожу кусок алгоритма:
25. If hasDuplicates is true, then a. Perform ? IteratorBindingInitialization of formals with iteratorRecord and undefined as arguments. 26. Else, a. Perform ? IteratorBindingInitialization of formals with iteratorRecord and env as arguments.
25) Если hasDuplicates это true, тогда выполнить IteratorBindingInitialization для formals с передачей iteratorRecord и значением undefined в качестве аргументов 26) В противном случае, выполнить IteratorBindingInitialization для formals с передачей iteratorRecord и env в качестве аргументов.
Можем выбрать один из перечисленных шагов, принципиальной разницы не будет (разница описана в преамбуле).
Этап 3: Производства формальных параметров и итерация
Этот шаг я бы назвал квинтэссенцией знаний о понимании производств, умении их расшифровывать и знании о том как происходит итерация с присвоением к какждому идентификатору значения.
Пример я приведу на более сложном образце для лучшего понимания, а образец который использовался ранее просто будет.
Итак допустим формальные параметры это (a,[aa,bb,obj],b)
; а аргументы которые переданы при вызове функций для этих параметров такие (1,[11,22,{a:"str"}],2)
.
На основе этого начинаем; семантика IteratorBindingInitialization смотрим. Мы знаем что верхнее производство грамматики для формальных параметров именуется как FormalParameters
, пытаемся найти и находим FormalParameters: FormalParameterList, FunctionRestParameter
, но это не подходит к нашему примеру так как у нас нет rest
синтаксиса в нашем примере, смотрим есть ли что-то подхоядщее что нас бы устроило и не находим, как так?
А дело вот в чем, здесь применяется принцип DRY (Do not Repeat Yourself), так как некоторые производства имеют схожий принцип разбиения либо содержат однозначную интерпретацию, указывается нижняя грамматика и по ней происходит чтение. Что же в нашем случае, в нашем случае мы смотрим нижнюю грамматику: FormalParameterList: FormalParameterList, FormalParameter
, это не похоже на производство FormalParameters
, но она дает тот же результат.
Смотрим алгоритм этой грамматики:
1. Perform ? IteratorBindingInitialization of FormalParameterList using iteratorRecord and environment as the arguments. 2. Return the result of performing IteratorBindingInitialization of FormalParameter using iteratorRecord and environment as the arguments.
Перевод:
1. Выполнить IteratorBindingInitialization для FormalParameterList используя iteratorRecord и environment в качестве аргументов 2. Вернуть результат выполнения IteratorBindingInitialization для FormalParameter используя iteratorRecord и environment в качестве аргументов
Пояснение: Я надеюсь вы понимаете что эта грамматика рекурсивна для того чтобы предоставить n-кол параметров.
Первый шаг похож на шаг из FunctionDeclarationInstantiation
только здесь он уже просит читать вложенную грамматику при этом аргументы остаются те что и были переданы из FunctionDeclarationInstantiation
шага. Второй шаг один в один такой же как первый, только он уже просит вернуть результат выполнения. Ничего особого не возвращается но это Completion
(спецификационный тип), он сообщает о том как происходит выполнение механизма (в данной ситуации это вообще не важно для нас).
Я показал вам один из тех алгоритмов, которые будут разбивать на структуры на более мелкие производства, которые будут в последствии использованы алгоритмами, которые будут делать важные структурные штуки.
Я не буду раскладывать каждый этап, это долго, поэтому дам сайт, который занимается построением AST структуры. На этом сайте можете потестировать разичный код и посмотреть как он разбивается, на какие именно производства. Я думал как продемонстрировать но увы это слишком громоздко получается, поэтому я покажу отдельные состояние таких разложений:
FormalParameters
:(a,[aa,bb,obj],b)
FormalParameterList, FormalParameter
:(a,[aa,bb,obj]), (b)
FormalParameter
илиBindingElement
:(a)
FormalParameter
илиBindingElement
:([aa,bb,obj])
FormalParameter
илиBindingElement
:(b)
SingleNameBinding
илиBindingIdentifier Initializer opt
:(a)
BindingPattern Initializer opt
:([aa,bb,obj])
SingleNameBinding
илиBindingIdentifier Initializer opt
:(b)
BindingIdentifier
:(a)
ArrayBindingPattern
:([aa,bb,obj])
BindingIdentifier
:(b)
[BindingElementList]
:(aa,bb,obj)
BindingElementList
:(aa,bb,obj)
BindingElementList, BindingElisionElement
:(aa,bb), (obj)
BindingElisionElement
илиElision opt BindingElement
:(aa)
BindingElisionElement
илиElision opt BindingElement
:(bb)
BindingElisionElement
илиElision opt BindingElement
:(obj)
BindingElement
илиSingleNameBinding
:(aa)
BindingElement
илиSingleNameBinding
:(bb)
BindingElement
илиSingleNameBinding
:(obj)
SingleNameBinding
илиBindingIdentifier Initializer opt
:(aa)
SingleNameBinding
илиBindingIdentifier Initializer opt
:(bb)
SingleNameBinding
илиBindingIdentifier Initializer opt
:(obj)
BindingIdentifier
:(aa)
BindingIdentifier
:(bb)
BindingIdentifier
:(obj)
Смотрим на итог разбития:BindingIdentifier
:(a)
BindingIdentifier
:(aa)
BindingIdentifier
:(bb)
BindingIdentifier
:(obj)
BindingIdentifier
:(b)
Надеюсь это наглядно показывает преобразования, в результате у нас есть отдельные идентификаторы без всяких скобок и массивов. Я показал как раскладывается все это дело, но нужно же еще и алгоритмы применить. Как обычно пройдемся по ключевым моментам.
В первую очередь нас интересует производство SingleNameBinding: BindingIdentifier Initializer opt
каким бы параметр навороченным не был он в итоге превращается в данное производство.
Алгоритм:
1. Let bindingId be StringValue of BindingIdentifier. 2. Let lhs be ? ResolveBinding(bindingId, environment). 3. Let v be undefined. 4. If iteratorRecord.[[Done]] is false, then a. Let next be IteratorStep(iteratorRecord). b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. c. ReturnIfAbrupt(next). d. If next is false, set iteratorRecord.[[Done]] to true. e. Else, i. Set v to IteratorValue(next). ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. iii. ReturnIfAbrupt(v). 5. If Initializer is present and v is undefined, then a. If IsAnonymousFunctionDefinition(Initializer) is true, then i. Set v to the result of performing NamedEvaluation of Initializer with argument bindingId. b. Else, i. Let defaultValue be the result of evaluating Initializer. ii. Set v to ? GetValue(defaultValue). 6. If environment is undefined, return ? PutValue(lhs, v). 7. Return InitializeReferencedBinding(lhs, v).
Перевод:
1. Пусть bindingId будет значением строкового типа от производства BindingIdentifier 2. Пусть lhs будет результатом операции ResolveBinding(bindingId, environment) 3. Пусть v будет значением undefined 4. Если iteratorRecord.[[Done]] это false, тогда a. Пусть next будет результатом операции IteratorStep(iteratorRecord) b. Если next это прерванное завершение, установить iteratorRecord.[[Done]] в значение true c. Выполнить операцию ReturnIfAbrupt(next) d. Если next это false, установить iteratorRecord.[[Done]] в значение true e. Иначе, i. Установить v в значение результата выполнения операции IteratorValue(next) ii. Если v это прерванное завершение, установить interatorRecord.[[Done]] в значение true iii. Выполнить операцию ReturnIfAbrupt(v) 5. Если производство Initializer предоставлен и v это undefined, тогда a. Если IsAnonymousFunctionDefinition(Initializer) это true, тогда i. Установить v в результат выполнения NamedEvaluation производства Initializer с аргументом bindingId b. Иначе, i. Пусть defaultValue будет результатом оценивания производства Initializer ii. Установить v в результат выполнения операции GetValue(defaultValue) 6. Если environment это undefined, вернуть результат выполнения операции PutValue(lhs, v) 7. Вернуть результат выполнения операции InitializeReferencedBinding(lhs, v)
Пояснение: Тут надо вспомнить что мы ранее создали генератор, который хранится в записи IteratorRecord
, скоро мы о нем поговорим. Из этого алгоритма нам надо знать следующие вещи (по шагам):
- Шаг 1, получает значение переменной в строковом формате
- Шаг 2, делает операцию разрешения привязки, этот этап на самом деле фундаментальный он отвечает за разрешение имен переменных, результатом этой операции будет специальный спецификационный тип
Reference
- тоже фундаментальная вещь в ECMAScript, именно эта вещь объясняет каким образом мы можем писать что-то вроде этого"hello".split("")
- Шаг 4 нам показывает каким боком здесь участвует итератор аргументов, операция
IteratorStep
вызывается с созданным итератором в алгоритмеFunctionDeclarationInstantiation
. Этот вызов эквивалентен вызову в кодеmyIterator.next()
. Рассмотреть алгоритмIteratorStep
можно самостоятельно. Стоит лишь знать что он возвращает объект (по протоколу итератора) либоfalse
значение. После того как мы сделалиIteratorStep
и сохранили его в переменнуюnext
. Мы видим шаг где в переменнуюv
присваивается результат выполнения операцииIteratorValue(next)
, по сути операция просто достает из нашегоnext
свойствоvalue
и присваивает в соответствующую переменную. Подробнее обIteratorStep
в главе 3 (ниже) - Шаг 6 с помощью операции
PutValue
кладет в результат выполнения второго шага значение из шага 4.e.i (случай когда в списке параметров есть параметры с одинаковыми именами) либо седьмой шаг с помощью операцииInitializeReferencedBinding
(случай когда повторяющих имен в параметрах не замечено), таким образом первый параметр получил значение, теперь параметрa
равен1
.
Резюме: Если бы у нас был простой список параметров как (a,b,c)
, все свелось бы к повтору алгоритма выше три раза так как (SingleNameBinding, SingleNameBinding, SingleNameBinding)
.
Теперь давайте закончим рассматривать наш пример который я показывал выше, а именно: (a,[aa,bb,obj],b)
. С идентификатором a
мы разобрались осталось разобраться с параметрами представленные в виде структуры литерала массива. На помощь нам приходит следующее производство, которое раскрывает структуры {}
и []
, это BindingElement: BindingPattern Initializer opt
Алгоритм:
1. Let v be undefined. 2. If iteratorRecord.[[Done]] is false, then a. Let next be IteratorStep(iteratorRecord). b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. c. ReturnIfAbrupt(next). d. If next is false, set iteratorRecord.[[Done]] to true. e. Else, i. Set v to IteratorValue(next). ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. iii. ReturnIfAbrupt(v). 3. If Initializer is present and v is undefined, then a. Let defaultValue be the result of evaluating Initializer. b. Set v to ? GetValue(defaultValue). 4. Return the result of performing BindingInitialization of BindingPattern with v and environment as the arguments.
Перевод:
1. Пусть v будет undefined 2. Если iteratorRecord.[[Done]] это false, тогда a. Пусть next будет результатом операции IteratorStep(iteratorRecord) b. Если next это прерванное завершение, установить iteratorRecord.[[Done]] в значение true c. Выполнить операцию ReturnIfAbrupt(next) d. Если next это false, установить iteratorRecord.[[Done]] в значение true e. Иначе, i. Установить v в результат операции IteratorValue(next) ii. Если v это прерванное завершение, установить iteratorRecord.[[Done]] в значение true iii. Выполнить операцию ReturnIfAbrupt(v) 3. Если производство Initializer предоставлено и v имеет значение undefined, тогда a. Пусть defaultValue будет результатом оценивания производства Initializer b. Установить v в результат выполнения операции GetValue(defaultValue) 4. Вернуть результат выполнения BindingInitialization производства BindingPattern с переданными в него аргументами v и environment.
Пояснение/резюме: Заметьте второй шаг идентичен шагу четыре из предыдущего алгоритма. А это означает что итератор используется одинаково в двух разных производствах. Но есть один нюанс, в этом алгоритме последний шаг, говорит о том что нужно анализировать производства дальше от алгоритма выше. Этот этап был проделан для того чтобы получить значение второго элемента нашего итератора и с ним продолжить разбивать переменные. Еще обратите внимание в этом алгоритме нет операций связанных с тем чтобы присвоить нашим параметрам значения.
Итак итератор смещен производством выше на значение [11,22,{a:"str"}]
, по сути это просто массив с элементами. Но здесь он будет играть роль дополнительных значений которые мы хотим присвоить нашим параметрам.
Последний шаг предыдущего алгоритма отправил нас на такое производство и алгоритм:
BindingPattern : ArrayBindingPattern
1. Let iteratorRecord be ? GetIterator(value). 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result). 4. Return result.
Перевод:
1. Пусть iteratorRecord будет результатом операции GetIterator(value) 2. Пусть result будет результатом вычисления IteratorBindingInitialization производства ArrayBindingPattern с переданными в него аргументами iteratorRecord и environment. 3. Если iteratorRecord.[[Done]] это false, вернуть результат операции IteratorClose(iteratorRecord, result) 4. Вернуть значение result.
Пояснение:
- Суть этого алгоритма создать структуру которая будет работать как итератор. Если попытаться что-то подобное сделать в самом ECMAScript то как пример (сокращенный код, частично соответствует спецификации):
const obj = [1,2,3];
const method = Reflect.get(obj, Symbol.iterator);
const iterator = method.call(obj);
const nextMethod = Reflect.get(iterator, "next");
const record = {"[[Iterator]]": iterator, "[[NextMethod]]": nextMethod, "[[Done]]": false};
console.log(record);
- Этот шаг использует производства что говорит нам о том что мы переходим в фазу самоподобия, и теперь используя этот итератор мы будем работать с
11,22,{a:"str"}
- Этот шаг закроет итератор который мы создали (взяли) если его специальное поле
[[Done]]
будет равноfalse
, операция закрытияiteratorClose
. - Возврат к старому итератору созданному на этапе
FunctionDeclarationInstantiation
.
Мы знаем что у нас появился новый итератор, который перечисляет элементы в массиве, теперь нужно увидеть на каких производствах он сработает. Предыдущий алгоритм рассматривал ([11,22,{a:"str"}])
как BindingPattern: ArrayBindingPattern
. Раскладываю на производства (алгоритмы не учитываю, они транзитные, хотя глянуть можно):
BindingPattern: ArrayBindingPattern BindingElementList: BindingElementList, BindingElisionElement SingleNameBinding: BindingIdentifier Initializer opt SingleNameBinding: BindingIdentifier Initializer opt SingleNameBinding: BindingIdentifier Initializer opt
Почему так? Многие из производств показывают метку opt
это означает, что производство может отсутствовать, всвязи с этим некоторые алгоритмы без меток opt
отлетают так как они показывают обязательность. Ну и плюс я говорил про DRY, в спецификации любят сокращать вещи, и потом получается неочевидно.
Итак вы видите что в итоге мы получаем три производства SingleNameBinding
, а его алгоритм вам известен. После того как завершится перечисление внутреннего итератора мы будем иметь переменные: a = 1, aa = 11, bb = 22, obj = {a:"str"}
. После закроется вложенный итератор и мы вернемся на уровень итератора из FunctionDeclarationInstantiation
. Далее останется последний раз сделать итерацию по SingleNameBinding
производству. Все и теперь мы имеем полный список параметров инициализированных нашей записью аргументами a = 1, aa = 11, bb = 22, obj = {a:"str"}, c = 2
теперь когда будет происходить, выполнение тела функции эти переменные можно будет использовать.
Все, дальше рассматривать алгоритм FunctionDeclarationInstantiation
не имеет смысла, больше он ничего не делает с формальными параметрами.
На этом с деструктуризацией в формальных параметров покончено.
Глава 2: Встреча с оппонентом и устранение
Теперь вы чуть ли не дословно знаете как работает деструктуризация в формальных параметрах. Осталось выяснить как переданный массив в proxy
остается таким же, при этом используя деструктуризацию для второго аргумента в методе construct
.
Наш субъект:
const trapsObject = {
construct(target, [args]){
console.log(args);
return {};
}
};
const Exo = new Proxy(Array, trapsObject);
new Exo([1,2,3]);
Чтобы понять как он проворачивает этот трюк с массивом надо узнать его боевую технику. Мы видим что он использует proxy
. Давайте посмотрим на алгоритм Proxy
1. If NewTarget is undefined, throw a TypeError exception. 2. Return ? ProxyCreate(target, handler).
В переводе это не нуждается, мы видим что нам нужно выполнить подоперацию ProxyCreate смотрим ее
Алгоритм:
1. If Type(target) is not Object, throw a TypeError exception. 2. If Type(handler) is not Object, throw a TypeError exception. 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »). 4. Set P's essential internal methods, except for [[Call]] and [[Construct]], to the definitions specified in 10.5. 5. If IsCallable(target) is true, then a. Set P.[[Call]] as specified in 10.5.12. b. If IsConstructor(target) is true, then i. Set P.[[Construct]] as specified in 10.5.13. 6. Set P.[[ProxyTarget]] to target. 7. Set P.[[ProxyHandler]] to handler. 8. Return P.
Перевод:
1. Если Type(target) не является типом Object, выбросить исключение типа TypeError 2. Если Type(handler) не является типом Object, выбросить исключение типа TypeError 3. Пусть P будет результатом выполнения операции MakeBasicObject(«[[ProxyHandler]], [[ProxyTarget]]») 4. Установить важные внутренние методы P, исключая [[Call]] и [[Construct]] в определения указанные в 10.5 5. Если IsCallable(target) это true, тогда a. Установить P.[[Call]] как указано в 10.5.12 b. Если IsConstructor(target) это true, тогда i. Установить P.[[Construct]] как указано в 10.5.13 6. Установить P.[[ProxyTarget]] в значение из target 7. Установить P.[[ProxyHandler]] в значение из handler 8. Вернуть значение из P.
Пояснение: На самом деле ничего необычного нет, стоит принять только во внимания поля [[ProxyTarget]]
и [[ProxyHandler]]
. Но 4 шаг если быть не особо внимательным можно пропустить, а там как раз указывается глава с методами, которые и отличают объект, который конструируется как proxy
.
Чтобы понять как и где вступает в битву proxy
нам нужно выяснить откуда проистекает начало. А идет оно от оператора new. Изучение данного метода я отдаю полностью в ваши руки. Я лишь скажу что когда будет вызываться операция [[Construct]]
, этот метод будет взят у proxy
отсюда вот он и поменяет управление с привычного нам вызова [[Construct]]
отсюда (обычно данный [[Construct]]
используется при создании различных объектов, но не в случае с proxy
)
Пример того как будет вызыван данный [[Construct]]
на нашем примере: Exo.[[Construct]]
([1,2,3])
Теперь рассмотрим данный [[Construct]]
из proxy
:
1. Let handler be O.[[ProxyHandler]]. 2. If handler is null, throw a TypeError exception. 3. Assert: Type(handler) is Object. 4. Let target be O.[[ProxyTarget]]. 5. Assert: IsConstructor(target) is true. 6. Let trap be ? GetMethod(handler, "construct"). 7. If trap is undefined, then a. Return ? Construct(target, argumentsList, newTarget). 8. Let argArray be ! CreateArrayFromList(argumentsList). 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). 10. If Type(newObj) is not Object, throw a TypeError exception. 11. Return newObj.
Перевод:
1. Пусть handler будет значением из O.[[ProxyHandler]] 2. Если handler это null, выбросить исключение типа TypeError 3. Утверждаю: Type(handler) это тип Object 4. Пусть target будет O.[[ProxyTarget]] 5. Утверждаю: IsConstructor(target) это true 6. Пусть trap будет результатом операции GetMethod(handler, "constructor") 7. Если trap это undefined, тогда a. Вернуть результат операции Construct(target, argumentList, newTarget) 8. Пусть argArray будет результатом операции CreateArrayFromList(argumentsList) 9. Пусть newObj будет результатом выполнения операции Call(trap, handler, «target, argArray, newTarget») 10. Если Type(newObj) это не тип Object, выбросить исключение типа TypeError 11. Вернуть значение из newObj
Пояснение: O
- это объект, на котором вызывается операция [[Construct]]
в данном случае это наш proxy
объект. Из всех шагов алгоритма нас интересуют шаги: 6, 8, 9, 11
- Шаг 6, вынимает из второго аргумента нашего
proxy
объекта методconstruct
. - Шаг 8, создает из спецификационного типа
List
, массив аргументов. Помните что у нас список аргументов состоит из одного элемента и это[1,2,3]
, так вот этот элемент будет еще раз обернут в массив и получится[[1,2,3]]
- Шаг 9, делает спецификационный вызов с аргументами, тут детальнее.
trap
- выступает в роли функции, которую вызывают,handler
- объектом на котором вызывают, и третий аргумент это опять спецификационныйList
несмотря на предыдущий шаг, но в него передаетсяtarget
- это первый аргумент при созданииproxy
,argArray
- массив аргументов иnewTarget
- специальный указатель цели (основное назначение указывать цель при наследовании в конструкторах). Вот так все это дело будет вызвано через внутренний метод[[Call]]
(тут уже он обычный, так как вызов идет не отproxy
):trap.[[Call]](handler, «target, argArray, newTarget»)
. Кодом будет выглядеть так:
const trapsObject = {
construct(target, [args]){
console.log(args);
return {};
}
};
const Exo = new Proxy(Array, trapsObject);
const trap = Reflect.get(trapsObject, "construct") /// шестой шаг из алгоритма выше, но написанный по-другому, так как невозможно написать также как в спецификации
Reflect.apply(trap, trapsObject, [Array, [[1,2,3]], Exo]) /// девятый шаг
По сути происходит контекстуальный вызов определенного нами метода "construct"
, передаваемые аргументы вам уже известны, а то как раскладываются формальные параметры вы тоже знаете. Сложностей возникнуть не должно.
4) Шаг 11, возвращаем результат который получился на предыдущем шаге.
Глава 3: Вакидзаси самурая / yield
Итак здесь пойдет история о том что же такое yield
в генераторах и почему оно в нашем случае тоже присутствует несмотря на отсутствие явных записей.
Вспомните производство SingleNameBinding: BindingIdentifier Initializer opt
и его алгоритм. В его алгоритме есть шаг 4.a, который вызывает IteratorStep
операцию (ее мы не рассматривали ранее). Давайте взглянем на этот алгоритм.
Алгоритм:
1. Let result be ? IteratorNext(iteratorRecord). 2. Let done be ? IteratorComplete(result). 3. If done is true, return false. 4. Return result.
Перевод:
1. Пусть result будет результатом операции IteratorNext(iteratorRecord) 2. Пусть done будет результатом операции IteratorComplete(result) 3. Если done это true, вернуть false 4. Вернуть result
Пояснение:
- Шаг 1, вызывает операцию
IteratorNext
(не будем рассматривать), она берет изiteratorRecord
поле[[NextMethod]]
, также[[Iterator]]
и делает такую операцию[[NextMethod]].[[Call]]
([[Iterator]])
. По сути это опять же обычный вызовmyIterator.next()
на генераторе с возвратом протокольного объекта, с полямиvalue
иdone
- Шаг 2, смотрит у результата первого шага свойство
done
и сохраняет его в переменнуюdone
- Шаг 3, проверяет переменную
done
, возвращаетfalse
если, итератор подошел к логическому концу со свойствомdone:true
- Шаг 4, возвращает протокольный объект
По сути мы рассмотрели операцию, которая вынуждает итерироваться, но никакого yield
еще не было. На самом деле онa былa только инкогнито или неявно. Механизм итерации происходит через механизмы генераторов. Когда условно вы в пишите myIterator.next()
вы во-первых вызываете метод next
, а во вторых используете код функции генератора. Наш случай не исключение. Метод next
у нас вызывается, а код функции кстати находится здесь в шаге 1. Вот этот код функции срабатывает каждый раз когда вызывается IteratorNext
.
Давайте взглянем сначала на метод next
, а потом на производство yield
Алгоритм next:
1. Return ? GeneratorResume(this value, value, empty).
Из него вытекает подоперация GeneratorResume, смотрим:
Алгоритм:
1. Let state be ? GeneratorValidate(generator, generatorBrand). 2. If state is completed, return CreateIterResultObject(undefined, true). 3. Assert: state is either suspendedStart or suspendedYield. 4. Let genContext be generator.[[GeneratorContext]]. 5. Let methodContext be the running execution context. 6. Suspend methodContext. 7. Set generator.[[GeneratorState]] to executing. 8. Push genContext onto the execution context stack; genContext is now the running execution context. 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation. 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. 11. Return Completion(result).
Перевод:
1. Пусть state будет результатом операции GeneratorValidate(generator, generatorBrand) 2. Если state завершен, вернуть результат операции CreateIterResultObject(undefined, true) 3. Утверждаю: state либо имеет значение suspendedStart либо suspendedYield 4. Пусть genContext будет значением из generator.[[GeneratorContext]] 5. Пусть methodContext будет текущим запущенным исполнительным контекстом 6. Приостановить methodContext 7. Установить generator.[[GeneratorState]] в значение executing 8. Положить genContext на верхушку стека исполнительных контекстов; genContext сейчас запущенный исполнительный контекст 9. Продолжить приостановленную оценку genContext используя NormalCompletion(value) как результат операции которая приостановила его. Пусть result будет значением возвращаемое продолженным вычислением. 10. Утверждаю: Когда мы возвращаемся сюда, genContext уже удален из стека исполнительных контекстов и methodContext является текущим запущенным исполнительным контекстом 11. Вернуть результат операции Completion(result)
Пояснение: Большое внимание здесь уделяется управлением контекстами, но это рассказывать начиная прямо отсюда не имеет смысла. Но объяснить все же придется кое-что. Обратите внимание на шаг 8, он сообщает нам о том что достается контекст исполнения из генератора и теперь он является исполняющим. А это значит что код нашего итератора находится в активном состоянии.
Теперь мы находимся условно в стадии выполнении кода итератора. Обратите на внимание алгоритм:
1. Let closure be a new Abstract Closure with no parameters that captures list and performs the following steps when called: a. For each element E of list, do i. Perform ? Yield(E). b. Return undefined.
Как подобный код выглядел бы через реальные генераторы:
function *gen(list){
for(const e of list){
yield e;
}
}
const it_gen = gen([1,2,3]);
Есть конкретное производство отвечающее за yield, которое перетекает в вызов Yield операции.
Алгоритм:
1. Let generatorKind be ! GetGeneratorKind(). 2. If generatorKind is async, return ? AsyncGeneratorYield(value). 3. Otherwise, return ? GeneratorYield(! CreateIterResultObject(value, false)).
Перевод:
1. Пусть generatorKind будет результатом выполнения GetGeneratorKind() 2. Если generatorKind это async, вернуть результат выполнения AsyncGeneratorYield(value) 3. В противном случае, вернуть результат GeneratorYield(CreateIterResultObject(value, false))
Пояснение: Операция предназначена для того чтобы определить тип генератора и выбрать для него определенный способ обработки кода. Нас волнует синхронный путь, а это третий шаг алгоритма операция GeneratorYield, в этот алгоритм передается значение из операции Yield(value)
и значение false
, все вместе это превращается в объект со свойствами value
и done
которые следуют протоколу итератора.
Ну-с взглянем на алгоритм GeneratorYield
.
Алгоритм:
1. Let genContext be the running execution context. 2. Assert: genContext is the execution context of a generator. 3. Let generator be the value of the Generator component of genContext. 4. Assert: GetGeneratorKind() is sync. 5. Set generator.[[GeneratorState]] to suspendedYield. 6. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. 7. Set the code evaluation state of genContext such that when evaluation is resumed with a Completion resumptionValue the following steps will be performed: a. Return resumptionValue. b. NOTE: This returns to the evaluation of the YieldExpression that originally called this abstract operation. 8. Return NormalCompletion(iterNextObj). 9. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of genContext.
Перевод:
1. Пусть genContext будет запущенным активным контекстом исполнения 2. Утверждаю: genContext это исполнительный контекст генератора 3. Пусть generator будет значением комоненты Generator принадлежащего genContext 4. Утверждаю: GetGeneratorKind() это sync 5. Установить generator.[[GeneratorState]] в значение suspendedYield 6. Удалить genContext из стека исполнительных контекстов и восстановить исполнительный контекст который находится на вершине стека исполнительных контекстов как запущенный активный контекст исполнения. 7. Установить состояние оценки кода genContext таким образом чтобы когда оценивание приостановлено с помощью Completion resumptionValue следующие шаги должны будут выполнены: a. Вернуть resumptionValue b. Примечание: Это возвращает к оцениванию производства YieldExpression, что изначально вызвало эту абстрактную операцию 8. Вернуть результат операции NormalCompletion(iterNextObj) 9. Примечание: Это возвращает к оцениванию операции которая ранее всего возобновила оценивание genContext
Пояснение: Этот этап приостанавливает код функции генератора-итератора, на производстве yield
и возвращает значение переданное yield
, также этот алгоритм сообщает что при следующем вызове yield
оценка кода должна быть продолжена на том месте где остановилась при этом передавая значение. Конечно по окончанию этого алгоритма мы возвращаемсяя в код который инициировал вызов next
метода.
Как-то так работает механизм yield
.
На этом конец всех глав.
Послесловие: Это было реально сложно и много, надеюсь вам это как-то помогло и открыло окно овертона, если нет тогда надеюсь что вам было хотя бы интересно. Я ничего подобного не писал, да и времени ушло на неделю. Теперь такое точно больше писать не буду (может что под заказ), так как выгоднее объяснять отдельно конкретные темы, хотя здесь я попытался сделать что-то среднее. Как обычно жду вопросы, а здесь их могут быть сотни :)
Если нашли парадоксы, оксюмороны пишите в комментариях, обсудим. Если имеется желание отредактировать, буду рад этому (оставлю вашу подпись редактора)
I wanna go and take a shot of something after that tremendous work :)