Как выглядит Babel из серии ES6, компилирующий Generator

внешний интерфейс JavaScript Promise Babel
Как выглядит Babel из серии ES6, компилирующий Generator

предисловие

Эта статья предназначена для краткого ознакомления с кодом синтаксиса генератора.

Generator

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

Выводим результат выполнения:

var hw = helloWorldGenerator();

console.log(hw.next()); // {value: "hello", done: false}
console.log(hw.next()); // {value: "world", done: false}
console.log(hw.next()); // {value: "ending", done: true}
console.log(hw.next()); // {value: undefined, done: true}

Babel

Конкретный процесс выполнения упоминать не будем, перейдем сразу на официальный сайт Babel.Try it outВставьте приведенный выше код и посмотрите, как выглядит код после компиляции:

/**
 * 我们就称呼这个版本为简单编译版本吧
 */
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(
    function helloWorldGenerator$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            _context.next = 2;
            return "hello";

          case 2:
            _context.next = 4;
            return "world";

          case 4:
            return _context.abrupt("return", "ending");

          case 5:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    this
  );
}

На первый взгляд кажется, что скомпилированный код совсем небольшой, но если присмотреться, скомпилированный код определенно непригоден для использования.regeneratorRuntimeЧто это за фигня? Где заявление?markа такжеwrapМетоды и делать что?

Разве нельзя было скомпилировать полностью пригодный для использования код?

regenerator

Если вы хотите увидеть полный рабочий код, вы можете использоватьregenerator, инструмент под Facebook для составления функций генератора ES6.

Сначала установим регенератор:

npm install -g regenerator

Затем создаем новый файл generate.js, код внутри это код в начале статьи, выполняем команду:

regenerator --include-runtime generator.js > generator-es5.js

Мы можем увидеть скомпилированный полный и доступный код в файле генератора-es5.js.

А эта компиляция компилирует более 700 строк... Скомпилированный код можно посмотретьgenerator-es5.js

Короче говоря, скомпилированный код достаточно сложен, из него можно извлечь общую логику, хотя бы простой скомпилированный код пусть работает.

функция отметки

Первый раздел простого скомпилированного кода выглядит так:

var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);

Давайте посмотрим на исходный код функции mark в полной скомпилированной версии:

runtime.mark = function(genFun) {
  genFun.__proto__ = GeneratorFunctionPrototype;
  genFun.prototype = Object.create(Gp);
  return genFun;
};

Это также касается переменных GeneratorFunctionPrototype и Gp, мы также смотрим на соответствующий код:

function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}

...

var Gp = GeneratorFunctionPrototype.prototype =
  Generator.prototype = Object.create(IteratorPrototype);

GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;

GeneratorFunctionPrototype.constructor = GeneratorFunction;

GeneratorFunctionPrototype[toStringTagSymbol] =
  GeneratorFunction.displayName = "GeneratorFunction";

Этот код создает кучу, казалось бы, сложных цепочек отношений, на самом деле это ссылка наСпецификация ES6Выстроена цепочка отношений:

regenerator

на фото+@@toStringTag:s = 'Generator'это Гп,+@@toStringTag:s = 'GeneratorFunction'Один из них — GeneratorFunctionPrototype.

Цель построения цепочки отношений состоит в том, чтобы соответствовать оригиналу при оценке отношений, таких как:

function* f() {}
var g = f();
console.log(g.__proto__ === f.prototype); // true
console.log(g.__proto__.__proto__ === f.__proto__.prototype); // true

Для простоты мы можем сначала установить пустой объект, но, как вы можете видеть на рисунке выше, следующим (), бросок (), возвращаемые () функции установлены на объектах GP, на самом деле в полном скомпилированном коде, GP действительно многообещающий способ добавления этих трех функций:

// 117 行
function defineIteratorMethods(prototype) {
  ["next", "throw", "return"].forEach(function(method) {
    prototype[method] = function(arg) {
      return this._invoke(method, arg);
    };
  });
}

// 406 行
defineIteratorMethods(Gp);

Для простоты упростим всю функцию отметки до:

runtime.mark = function(genFun) {
  var generator = Object.create({
    next: function(arg) {
      return this._invoke('next', arg)
    }
  });
  genFun.prototype = generator;
  return genFun;
};

функция переноса

В дополнение к настройке цепочки отношений возвращаемое значение genFun функции метки также передается в качестве второго параметра функции переноса:

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(
    function helloWorldGenerator$(_context) {
      ...
    },
    _marked,
    this
  );
}

Давайте еще раз посмотрим на функцию переноса:

function wrap(innerFn, outerFn, self) {
  var generator = Object.create(outerFn.prototype);
  var context = new Context([]);
  generator._invoke = makeInvokeMethod(innerFn, self, context);

  return generator;
}

Итак, при выполненииvar hw = helloWorldGenerator();Когда функция переноса фактически выполняется, функция переноса возвращает генератор, генератор является объектом, а прототипouterFn.prototype, outerFn.prototypeНа самом деле этоgenFun.prototype,genFun.prototypeпустой объект с методом next() в прототипе.

поэтому, когда вы выполняетеhw.next()Когда , выполнение на самом деле является следующей функцией на прототипе прототипа hw, а следующая функция выполняет функцию _invoke hw:

generator._invoke = makeInvokeMethod(innerFn, self, context);

innerFn — это функция, обернутая с помощью wrap, которая на самом деле является функцией helloWordGenerato$, о, это функция:

function helloWorldGenerator$(_context) {
  while (1) {
    switch ((_context.prev = _context.next)) {
      case 0:
        _context.next = 2;
        return "hello";

      case 2:
        _context.next = 4;
        return "world";

      case 4:
        return _context.abrupt("return", "ending");

      case 5:
      case "end":
        return _context.stop();
    }
  }
}

И контекст, вы можете напрямую понимать его как такой глобальный объект:

var ContinueSentinel = {};

var context = {
  done: false,
  method: "next",
  next: 0,
  prev: 0,
  abrupt: function(type, arg) {
    var record = {};
    record.type = type;
    record.arg = arg;

    return this.complete(record);
  },
  complete: function(record, afterLoc) {
    if (record.type === "return") {
      this.rval = this.arg = record.arg;
      this.method = "return";
      this.next = "end";
    }

    return ContinueSentinel;
  },
  stop: function() {
    this.done = true;
    return this.rval;
  }
};

каждый разhw.nextКогда значение атрибутов next и prev изменено, при возврате в функции-генераторе будет выполняться рывок, а в рывке будет выполняться полный, а будет выполнен полный, т.к.this.next = endПо этой причине функция остановки будет выполнена, если она будет выполнена снова.

Давайте посмотрим на функцию makeInvokeMethod:

var ContinueSentinel = {};

function makeInvokeMethod(innerFn, self, context) {
  var state = 'start';

  return function invoke(method, arg) {

    if (state === 'completed') {
      return { value: undefined, done: true };
    }

    context.method = method;
    context.arg = arg;

    while (true) {

      state = 'executing';

      var record = {
        type: 'normal',
        arg: innerFn.call(self, context)
      };
      if (record.type === "normal") {

        state = context.done
          ? 'completed'
          : 'yield';

        if (record.arg === ContinueSentinel) {
          continue;
        }

        return {
          value: record.arg,
          done: context.done
        };

      }
    }
  };
}

Основной процесс исполнения не анализируется, мы видим фокус третьего исполненияhw.next()когда:

Третье исполнениеhw.next(), реально реализовано

this._invoke("next", undefined);

Мы создали объект записи, вызывающий функцию:

var record = {
  type: "normal",
  arg: innerFn.call(self, context)
};

пока вinnerFn.call(self, context), поскольку _context.next равно 4, на самом деле выполняется:

_context.abrupt("return", 'ending');

И в резком, мы создаем еще один объект записи:

var record = {};
record.type = 'return';
record.arg = 'ending';

затем казненthis.complete(record),

в полной мере потому, чтоrecord.type === "return"

this.rval = 'ending';
this.method = "return";
this.next = "end";

Затем вернитесь к глобальному объекту ContinueSentinel, фактически глобальному пустому объекту.

затем в функции вызова, потому чтоrecord.arg === ContinueSentinelПо этой причине, не выполняя следующий оператор возврата, он напрямую входит в следующий цикл.

Так сделай это сноваinnerFn.call(self, context),В настоящее время_context.nextдля конца, выполнено_context.stop(), в функции остановки:

this.done = true;
return this.rval; // this.rval 其实就是 `ending`

Таким образом, окончательное возвращаемое значение:

{
  value: 'ending',
  done: true
};

После этого, когда мы снова выполняем hw.next(), поскольку состояние уже «завершено», оно возвращается напрямую{ value: undefined, done: true}

Неполный, но доступный исходный код

Конечно, этот процесс может быть трудно понять, читая текст. Неполный, но доступный код выглядит следующим образом. Вы можете выполнить отладку с помощью точек останова, чтобы просмотреть конкретный процесс:

(function() {
  var ContinueSentinel = {};

  var mark = function(genFun) {
    var generator = Object.create({
      next: function(arg) {
        return this._invoke("next", arg);
      }
    });
    genFun.prototype = generator;
    return genFun;
  };

  function wrap(innerFn, outerFn, self) {
    var generator = Object.create(outerFn.prototype);

    var context = {
      done: false,
      method: "next",
      next: 0,
      prev: 0,
      abrupt: function(type, arg) {
        var record = {};
        record.type = type;
        record.arg = arg;

        return this.complete(record);
      },
      complete: function(record, afterLoc) {
        if (record.type === "return") {
          this.rval = this.arg = record.arg;
          this.method = "return";
          this.next = "end";
        }

        return ContinueSentinel;
      },
      stop: function() {
        this.done = true;
        return this.rval;
      }
    };

    generator._invoke = makeInvokeMethod(innerFn, context);

    return generator;
  }

  function makeInvokeMethod(innerFn, context) {
    var state = "start";

    return function invoke(method, arg) {
      if (state === "completed") {
        return { value: undefined, done: true };
      }

      context.method = method;
      context.arg = arg;

      while (true) {
        state = "executing";

        var record = {
          type: "normal",
          arg: innerFn.call(self, context)
        };

        if (record.type === "normal") {
          state = context.done ? "completed" : "yield";

          if (record.arg === ContinueSentinel) {
            continue;
          }

          return {
            value: record.arg,
            done: context.done
          };
        }
      }
    };
  }

  window.regeneratorRuntime = {};

  regeneratorRuntime.wrap = wrap;
  regeneratorRuntime.mark = mark;
})();

var _marked = regeneratorRuntime.mark(helloWorldGenerator);

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(
    function helloWorldGenerator$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            _context.next = 2;
            return "hello";

          case 2:
            _context.next = 4;
            return "world";

          case 4:
            return _context.abrupt("return", "ending");

          case 5:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    this
  );
}

var hw = helloWorldGenerator();

console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());

серия ES6

Адрес каталога серии ES6:GitHub.com/ в настоящее время имеет бриз…

Ожидается, что в серии ES6 будет написано около 20 статей, направленных на углубление понимания некоторых точек знаний ES6, с акцентом на область действия на уровне блоков, шаблоны меток, функции стрелок, реализацию моделирования символов, наборов, карт и обещаний, схему загрузки модулей, асинхронность. обработка и т.п. содержание.

Если есть какие-либо ошибки или неточности, пожалуйста, поправьте меня, большое спасибо. Если вам нравится или у вас есть вдохновение, добро пожаловать в звезду, что также является поощрением для автора.

Категории