ES6之前,JS没有模块系统,无法将一个大程序拆分为互相依赖的小文件
社区制定了一些模块加载方案最主要的是CommonJS和AMD,前者用于服务器,后者用于浏览器。
// CommonJS模块,运行时加载
let { stat, exists, readFile } = require(‘fs‘);
ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。而上述两者方案只有在运行时才能确定。
// ES6模块,编译时已确定,效率更好
import { stat, exists, readFile } from ‘fs‘;
当然,这也导致了没法引用 ES6 模块本身,因为它不是对象
严格模式主要有以下限制。
with语句delete prop,会报错,只能删除属性delete global[prop]eval不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化arguments.calleearguments.callerthis指向全局对象,ES6模块中this指向undefinedfn.caller和fn.arguments获取函数调用的堆栈protected、static和interface)export命令用于规定模块的对外接口
// profile.js
// 输出变量、函数或类 export let a = ‘lpr‘ export let year = 2020
export function multiply(x, y) { return x * y; }
或者
// profile.js
let a = 111
function v1() { ... }
export { firstName, v1 }
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion // 可以用不同的名字输出
}
export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
commonJS模块输出的是值得缓存,不存在动态更新
export命令可以出现在模块的任何位置,只要处于模块顶层就可以
import命令用于输入其他模块提供的功能
import { a,b,c } from ‘./profile.js‘;
如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from ‘./profile.js‘;
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面改写接口。
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
import命令具有提升效果,会提升到整个模块的头部,首先执行
import是静态执行,所以不能使用表达式和变量
import { area, circumference } from ‘./circle‘;
// 整体加载
import * as circle from ‘./circle‘;
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
// export-default.js 默认输出一个匿名函数
export default function () {
console.log(‘foo‘);
}
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js import customName from ‘./export-default‘; customName(); // ‘foo‘
比较一下默认输出和正常输出。
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from ‘crc32‘; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from ‘crc32‘; // 输入
export { foo, bar } from ‘my_module‘;
// 可以简单理解为
import { foo, bar } from ‘my_module‘;
export { foo, bar };
但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。
如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from ‘./constants‘;
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {A, B} from ‘./constants‘;
console.log(A); // 1
console.log(B); // 3
传统方法
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
defer与async的区别是:defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺
浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。
<script type="module" src="./foo.js"></script>异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。
Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"。
如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module。
原文:https://www.cnblogs.com/Mijiujs/p/12117200.html