Skip to content

Instantly share code, notes, and snippets.

@dSalieri
Last active January 12, 2022 19:45
Show Gist options
  • Save dSalieri/417d0aaf6a57cebab8b76260f0a06a3f to your computer and use it in GitHub Desktop.
Save dSalieri/417d0aaf6a57cebab8b76260f0a06a3f to your computer and use it in GitHub Desktop.
Работа формальных параметров через спецификацию метода-ловушки construct у объекта Proxy

Вопрос: Почему мы должны применить деструктуризацию параметра чтобы получить массив который передаем при создании.

Код:

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. Шаг 1 является замыканием, и он не исполняется в тот момент когда мы читаем алгоритм, для программы которая исполняет эти шаги, этот шаг является просто кодом функции, которая сохраняется в переменную.
  2. Шаг 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

Итак резюме этого алгоритма:

  1. Создается объект в переменную generator, этот generator имеет специальные поля [[GeneratorState]], [[GeneratorContext]], [[GeneratorBrand]]. [[GeneratorState]] - отвечает за состояние генератора. [[GeneratorContext]] - отвечает за контекст генератора. [[GeneratorBrand]] - отвечает за производителя генератора. Таблица этих полей тут
  2. Можем заметить что на протяжении этого алгоритма меняются исполнительные контексты и это удивительно, потому что по факту мы не создаем вызовов дополнительных вызовов функций, которые могли бы породить эти контексты. Но факт есть факт, для переданного списка аргументов операция начинает производить контексты.
  3. Шаг 13 вызывает функцию GeneratorStart, которая является базисом вызова всех генераторных функций; позже рассмотрим.
  4. Шаг 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)

Резюме алгоритма:

  1. Алгоритм отвечает за первичный вызов генератора. Следовательно также играет контекстами.
  2. Большая часть алгоритма это Abstract Closure, которая замыкает genContext чтобы знать состояние оценки исполняемого кода.
  3. Устанавливает специальные значения в объект нашего генератора и возвращает ничего. Рассказывать про 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. Шаг 1, получает значение переменной в строковом формате
  2. Шаг 2, делает операцию разрешения привязки, этот этап на самом деле фундаментальный он отвечает за разрешение имен переменных, результатом этой операции будет специальный спецификационный тип Reference - тоже фундаментальная вещь в ECMAScript, именно эта вещь объясняет каким образом мы можем писать что-то вроде этого "hello".split("")
  3. Шаг 4 нам показывает каким боком здесь участвует итератор аргументов, операция IteratorStep вызывается с созданным итератором в алгоритме FunctionDeclarationInstantiation. Этот вызов эквивалентен вызову в коде myIterator.next(). Рассмотреть алгоритм IteratorStep можно самостоятельно. Стоит лишь знать что он возвращает объект (по протоколу итератора) либо false значение. После того как мы сделали IteratorStep и сохранили его в переменную next. Мы видим шаг где в переменную v присваивается результат выполнения операции IteratorValue(next), по сути операция просто достает из нашего next свойство value и присваивает в соответствующую переменную. Подробнее об IteratorStep в главе 3 (ниже)
  4. Шаг 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.

Пояснение:

  1. Суть этого алгоритма создать структуру которая будет работать как итератор. Если попытаться что-то подобное сделать в самом 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);
  1. Этот шаг использует производства что говорит нам о том что мы переходим в фазу самоподобия, и теперь используя этот итератор мы будем работать с 11,22,{a:"str"}
  2. Этот шаг закроет итератор который мы создали (взяли) если его специальное поле [[Done]] будет равно false, операция закрытия iteratorClose.
  3. Возврат к старому итератору созданному на этапе 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

  1. Шаг 6, вынимает из второго аргумента нашего proxy объекта метод construct.
  2. Шаг 8, создает из спецификационного типа List, массив аргументов. Помните что у нас список аргументов состоит из одного элемента и это [1,2,3], так вот этот элемент будет еще раз обернут в массив и получится [[1,2,3]]
  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. Шаг 1, вызывает операцию IteratorNext (не будем рассматривать), она берет из iteratorRecord поле [[NextMethod]], также [[Iterator]] и делает такую операцию [[NextMethod]].[[Call]]([[Iterator]]). По сути это опять же обычный вызов myIterator.next() на генераторе с возвратом протокольного объекта, с полями value и done
  2. Шаг 2, смотрит у результата первого шага свойство done и сохраняет его в переменную done
  3. Шаг 3, проверяет переменную done, возвращает false если, итератор подошел к логическому концу со свойством done:true
  4. Шаг 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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment