Статическая компиляция импорта/экспорта ES6

ECMAScript 6
Статическая компиляция импорта/экспорта ES6

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

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

Кроместатический анализ, я тоже слышалЗагрузка во время компиляции/статическая загрузкаИ т. д., всякий раз, когда я это вижу, у меня всегда возникает сомнение, означает ли это предложение, что определение/статический анализ зависимостей модуля ES6 находится вэтап предварительной компиляцииВы уверены?

предварительно скомпилировано

Когда я смотрел «Вы не знаете JavaScript, том 1», там было сказано:

Хотя JavaScript часто классифицируют как «динамический» или «интерпретируемый язык исполнения», на самом деле это компилируемый язык. Но в отличие от традиционных компилируемых языков, он не компилируется заранее, а скомпилированные результаты не переносятся между распределенными системами.

В традиционном скомпилированном языковом процессе часть исходного кода в программе проходит три этапа перед выполнением, которые в совокупности называются «компиляцией».

  • Токенизация/лексический анализ

    Разбивает строку символов на осмысленные (для языка программирования) куски кода, называемые лексическими единицами.var a = 2;Программа обычно разбивается на следующие лексические единицы:var, a, =, 2, ;

  • разбор/разбор

    Преобразует поток лексических единиц (массивов) в дерево элементов, представляющих грамматическую структуру программы, которое представляет собой абстрактное синтаксическое дерево (AST).

  • генерация кода

    Процесс преобразования AST в исполняемый код. Этот процесс тесно связан с языком/целевой платформой и т. д. Если оставить в стороне конкретику, краткий ответ заключается в том, что есть какой-то способvar a = 2;AST преобразуется в набор машинных инструкций для создания переменной с именем a (включая выделение памяти и т. д.) и сохранения значения в файле .

Для JavaScript в большинстве случаевКомпиляция происходит за несколько микросекунд (или даже меньше!) до выполнения кода..

Проще говоря, любой фрагмент кода JavaScript компилируется перед выполнением (обычно прямо перед выполнением). Поэтому компилятор JavaScript будетvar a = 2;Программа скомпилирована, затем готова к выполнению и обычно выполняется немедленно.

переменное продвижение

Давайте посмотрим на demo1

function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}
test();

// 打印
// undefined
// 2

DEMO1 имеет переменную подъемность во время прекомпилируемой фазы. После преобразования порядка исполнения становится так:

function test() {
    function foo() {
        return 2;
    }
    var a;
    console.log(a);
    console.log(foo());
    a = 1;
}
test();

Конкретная обработка на этапе перед компиляцией может относиться к тому, что было сделано ->Продвинутые основы внешнего интерфейса (3): подробное объяснение переменных объектов

ES6 import / export

импортировать переменную рекламу

Давайте посмотрим на demo2 и demo3:

demo2 - ES6

// a.js
console.log('I am a.js...')
import { foo } from './b.js';
console.log(foo);

// b.js
console.log('I am b.js...')
export let foo = 1;

// 运行 node -r esm a.js
// I am b.js
// I am a.js
// 1

demo3 - CommonJS

// a.js
console.log('I am a.js...')
var b = require('./b');
console.log(b.foo);

// b.js
console.log('I am b.js...')
let foo = 1;
module.exports = {
   foo: foo
}

// 运行 node a.js
// I am a.js
// I am b.js
// 1

demo2 сначала печатает «I am b.js», а demo3 сначала печатает «I am a.js».

В demo2, поскольку ES6 реализует функцию модуля на стандартном уровне языка, когда ключевое слово import встречается при предварительной компиляции a.js, b.js будет загружен первым, поэтому сначала будет выведено «I am b.js». Порядок выполнения a.js и b.js после предварительной компиляции следующий, весь процесс таков:Прекомпилировать a.js -> найти импорт ключевых слов -> прекомпилировать b.js -> выполнить b.js -> выполнить a.js.

// a.js
import { foo } from './b.js';
console.log('I am a.js...')
console.log(foo);

// b.js
console.log('I am b.js...')
export let foo = 1; // let 定义的变量不会提升

В demo3 при предварительной компиляции a.js будет расширено только объявление переменной b. Порядок выполнения a.js и b.js после предварительной компиляции следующий:

// a.js
var b;
console.log('I am a.js...')
b = require('./b');
console.log(b.foo);

// b.js
console.log('I am b.js...')
let foo = 1;
module.exports = {
   foo: foo
}

экспортировать переменную рекламу

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

Давайте посмотрим на demo4:

// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1; // const 定义的变量不能提升,但是前面有 export 后,可以提升声明部分。
export const bar2 = () => {
  console.log('bar2');
}
export function bar3() {
  console.log('bar3');
}

// b.js
export let foo = 1;
import * as a from './a';
console.log(a);

// 打印
// [Module] { bar: <uninitialized>, bar2: <uninitialized>, bar3: [Function: bar3] }
// a.js

ES6 Module VS CommonJS

ES6 вэтап предварительной компиляциидля загрузки модулей, пока CommonJS находится вэтап выполнениядля загрузки модулей (демо2 и демо3).

Модули ES6 выводят ссылку на значение, а модули CommonJS выводят копию значения.

Модуль ES6: когда движок JS предварительно скомпилирован, когда встречается ключевое слово import, будет создана ссылка только для чтения.Когда скрипт фактически выполняется, значение будет извлечено из загруженного модуля в соответствии со ссылкой только для чтения. Другими словами, если исходное значение изменится, значение импорта также изменится, и это значение не будет кэшироваться.

demo5

// a.js
import * as b from './b';
console.log(b.foo);
console.log(b.person);
setTimeout(() => {
  console.log(b.foo);
  console.log(b.person);
  import('./b').then(({ foo, person }) => {
    console.log(b.foo);
    console.log(b.person);
  });
}, 1000);

// b.js
export let foo = 1;
export let person = {
  name: 'tb'
}
setTimeout(() => {
  foo = 2;
  person.name = 'kiki'
}, 500);

// 打印
// 1
// { name: 'tb'}
// 2
// { name: 'kiki'}
// 2
// { name: 'kiki'}

Изменение пространства памяти gif:

CommonJS: После того, как модуль выполняется по требованию в первый раз, в памяти генерируется следующий объект, в котором значение экспорта является копией значения переменной внутри модуля.При использовании модуля значение будет получено экспортное имущество. Примечание: при повторном вызове require модуль выполняться не будет, а значение будет получено из экспортов, последующие изменения выходного значения внутри требуемого модуля на него не повлияют.

{
  id: '...', // 模块名
  exports: { ... }, // 输出值
  loaded: true, // 模块是否执行完毕
  ...
}

demo6

// a.js
var b = require('./b');
console.log(b.foo);
console.log(b.person);
setTimeout(() => {
  console.log(b.foo);
  console.log(b.person);
  console.log(require('./b').foo); // 测试多次 require 输出值是否还是第一次 require 的输出值
  console.log(require('./b').person);
})

// b.js
let foo = 1;
let person = {
  name: 'tb'
}
setTimeout(() => { // 测试模块内部改变输出值是否影响
  foo = 2;
  person.name = 'kiki';
}, 500);
module.exports = {
   foo,
   person
}

// 打印
// 1
// { name: 'tb'}
// 1
// { name: 'tb'}
// 1
// { name: 'tb'}

Изменение пространства памяти gif:

запуск es6 в узле

npm install esm // 我的 node 版本是 v8.14.0
node -r esm xxx.js // xxx.js 中使用 ES6 模块规范
node xxx.js        // xxx.js 中使用 CommonJS 规范

Ссылаться на

Глубокое понимание модулей ES6

Продвинутые основы внешнего интерфейса (3): подробное объяснение переменных объектов

Спецификации модулей CommonJS и ES6, которые вы должны знать

CommonJS vs ES6 import/export

Глубокое понимание механизма модуля ES6