乐趣区

关于javascript:javascript中的esm是什么

esm 是什么?

esm 是将 javascript 程序拆分成多个独自模块,并能 按需导入 的规范。和 webpack,babel 不同的是,esm 是 javascript 的规范性能,在浏览器端和 nodejs 中都已失去实现 。应用 esm 的益处是 浏览器能够最优化加载模块,比应用库更有效率。

esm 规范通过 import, export 语法实现模块变量的导入和导出。

esm 模块的特点

  • 存在模块作用域,顶层变量都定义在该作用域,内部不可见;
  • 模块脚本主动采纳严格模式;
  • 模块顶层的 this 关键字返回 undefined;
  • esm 是编译时加载,也就是只有所有 import 的模块都加载实现,才会开始执行;
  • 同一个模块如果加载屡次,只会执行一次。

export
export 语句用来导出模块中的变量。

// 导出变量
export let count = 1;
export const CONST_VAR = 'CONST_VAR';
// 导出函数
export function incCount() {count += 1;}
// 导出类
export class Demo {

}

function add(x) {return x + count;}
// 应用 export 导出一组变量
export {
    count,
    add,
    // 应用 as 重命名导出的变量
    add as addCount,
}

// 导出 default
export default add

// 合并导出其余模块的变量
export {name} from './esm_module2.js'
export * from './esm_module2.js'

import
import 语句用来导入其余模块的变量

// 导入变量
import {count, incCount, CONST_VAR} from './esm_module1.js';

// 通过 as 重命名导入的变量
import {addCount as renamedAddCount} from './esm_module1.js';

// 导入默认
import {default as defaultAdd} from './esm_module1.js';
import add from './esm_module1.js';

// 创立模块对象
import * as module1 from './esm_module1.js';

export 导出的是值援用
esm 模块和 commonjs 模块的一个显著差别是,cjs 导出的是值得拷贝,esm 导出的是值的援用。当模块外部的值被批改时,cjs 获取不到被批改后的值,esm 能够获取到被批改后的值。

cjs 例子

// cjs_module1.js
var count = 1;
function incCount() {count += 1;}

module.exports = {
    count: count,
    incCount: incCount,
}

// cjs_demo.js
var {count, incCount} = require('./cjs_module1.js');

console.log(count); // 1
incCount();
console.log(count); // 1

esm 例子

// esm_module1.js
let count = 1;
function incCount() {count += 1;}

export {
    count,
    incCount,
}

// esm_demo.js
import {count, incCount} from './esm_module1.js';

console.log(count); // 1
incCount();
console.log(count); // 2

从实现原理上来看,cjs 的 module.exports 是一个对象,在运行期注入模块。在导出语句 module.exports.count = count 执行时,是给这个对象调配一个 count 的键,并赋值为 1。这之后模块中的 count 变量再怎么变动,都不会烦扰到 module.exports.count

esm 中的 export {count}是导出了 count 变量的一个只读援用,等于说使用者读取 count 时,值的指向还是模块中 count 变量的值。

能够看阮一峰的这篇文章:ES6 入门教程

在 html 中应用 esm
应用 script 标签引入 esm 文件,同时设置 type=module,标识这个模块为顶级模块。浏览器将 esm 文件视为模块文件,辨认模块的 import 语句并加载。

<script src="./esm_main.js" type="module"></script>

如果不设置 type=module,浏览器认为该文件为一般脚本。查看到文件中存在 import 语句时,会报如下谬误:

esm 的加载机制
esm 规范没有规定模块的加载细节,将这些留给具体环境实现。大抵上分为上面四个步骤:

解析:实现读取模块的源代码并查看语法错误;

加载:递归加载所有 import 的模块;

链接:对每个加载的模块,都生成一个模块作用域,该模块下的所有全局申明都绑定到该作用域上,包含从其余模块导入的内容;

运行时:实现所有 import 的加载和链接,脚本运行每个曾经加载的模块中的语句。当运行到全局申明时,什么也不会做(在链接阶段曾经将申明绑定到模块作用域)。

能够看下 mdn 上的这篇深刻 esm 的文章:ES6 In Depth: Modules

动静加载模块
esm 的一个重要个性是编译时加载,这有利于引擎的动态剖析。加载的过程会先于代码的执行。却也导致 import 导入语句不能在函数或者 if 语句中执行:

// 报语法错误
if (true) {import add from './esm_module1.js';}

es2020 提案引入 import()函数,用来动静加载模块,并且能够用在函数和 if 语句中。

import('./esm_module1.js')
  .then(module => {console.log(module);
  })

import()函数承受要加载的模块相对路径,返回一个 Promise 对象,内容是要加载的模块对象。

应用 import()函数还能够实现依据变量动静加载模块

async function getTemplate(templateName) {let template = await import(`./templates/${templateName}`);
    console.log(template);
}

getTemplate("foo");
getTemplate("bar");
getTemplate("baz");
退出移动版