在 ES6前, 实现模块化使用的是 RequireJS 或 seaJS(分别是基于 AMD规范的模块化库,和基于 CMD规范的模块化库)。ES6则引入了模块化,其设计思想是尽量的静态化,使得在编译时就能确定模块的依赖关系,以及输出和输入的变量。
ES6的模块化分为导出(export) 与 导入(import)两个模块。通过 export命令显式指定导出的代码,再通过 import命令导入。
1、ES6的模块自动开启严格模式,不管模块头部是否有 “use strict” 字段。
2、模块中可以导出 和 导入各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
3、每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
4、每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,系统会直接从内存中读取。
// index_export.js // 一个模块就是一个独立的文件。该文件内的所有变量,对象和方法,外部都无法获取也无法使用。如果想使用该模块的变量、对象和方法,就需要用到 export关键字,将这些内容输出。 // 分别导出 export var year = 2020; export var month = '03'; export var day = 12; export var time = "21:34"; // 集体导出 var year = 2020; var month = '03'; var day = 12; var time = "21:34"; var testFunc = function () { console.log('index_export'); }; export {year, month, day, time,testFunc}; // 在 export命令后面,用大括号显示指定要输出的变量集合。与上一种写法(直接放在 var语句前)功能是一样的,但这种方式更优越。 // 因为将 export命令放在脚本尾部,一眼就能看清输出了哪些变量,那些方法,清晰明了。
1、export命令规定的是对外的接口,因此接口名必须与模块内部的变量名一一对应,排列顺序不需要一致,随便排都行。
// index_export.js // 以下是导出变量的三种写法,其他模块导入 index_export.js后就可以通过这个接口,取得变量 "year"了 export var year = 1992; var year = 1992; export {year}; var year = 1992; export {year as year_1992}; // 导出方法跟变量一样 export function testFunc() { console.log('index_export'); } function testFunc() { console.log('index_export'); } export {testFunc};
2、export命令可以出现在模块的任何位置,但必须处于模块顶层。如果命令处于块级作用域内,就会报错,因为处于条件代码块内时,就无法做静态优化了,违背了 ES6模块设计的初衷,下面的 import命令也是如此。
var year = 1992; function testFunc() { export default 'year' // Uncaught SyntaxError: Unexpected token 'export' } testFunc();
3、若不同模块导出的接口名称 变量名命名出现重复, 则可以使用 as重新命名变量名,同样下面的 import命令也是如此。
使用 export命令定义了模块的对外接口之后,其他 模块(JS文件)就可以通过 import命令加载这个模块,然后使用该模块内定义的变量、对象和方法。
只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import变量类型为对象的属性值,但不能改写 import变量类型为基本类型的值。
// index_export.js var year = 2020; var month = '03'; var day = 12; var time = { hour:21, minute:34, second:56 }; var testFunc = function () { document.getElementById("index_import").innerHTML = 'index_export'; }; export {year, month, day, time, testFunc};
// index_import.js // import命令,用于加载index_export.js文件,并从中导入变量、对象、方法等。import命令通过一对大括号导入其他模块输出的变量、对象、方法等,且大括号内的变量名,必须与被导入模块(index_export.js)对外接口的名称相同。 import {year, month, day, time, testFunc} from './index_export.js'; testFunc(); // import命令输入的变量原则上只读的,import命令本质是输入接口。 // 1、不允许在加载模块的脚本内,改写或赋值 被导入模块内的接口和常量。 year = 2020; // Uncaught TypeError: Assignment to constant variable. year已经被分配给一个常数,不允许修改。 // 2、允许在加载模块的脚本内,重新赋值 被导入模块内的 对象的属性。 // 这种方式虽然修改了被导入模块内的数据,但会导致其他模块(导入了该模块的模块)读取该值时也发生改变,且不易查错。所以建议凡是输入变量,能不动最好不动,免得遗患无穷。 time.hour = 11; console.log(time.hour); // 11
1、import命令会将导入的内容提升到整个模块的头部,首先执行。 import命令是在静态解析阶段执行,所以它是一个模块之中最早执行的,因此命令中不能使用表达式和变量,这些语法结构只有在代码运行时才会得到结果。
// 下面两种导入方式都是错误的,因为 import命令中用到了表达式、变量以及if结构,在静态分析阶段,这些语法都没有值,也就没法执行并取得结果。 import {year, month,100 + 1} from 'index_export.js'; // Uncaught SyntaxError: Unexpected number if (true) { // Unexpected token '{' var tempStr = 'tempStr'; import { year, tempStr + '123' } from 'index_export.js'; } else { import { year } from 'index_export.js'; }
2、如果重复执行同一条 import命令,则系统只会执行一次,而不会多次执行。import同一模块,声明不同接口引用,会声明对应变量,但只执行一次import 。
// index_import.js import {year} from 'index_export.js'; import {time} from 'index_export.js'; // 等同于 import {year, time} from 'index_export.js'; // 虽然 year和time 分别在两个 import命令中加载,但它们出自同一个index_export实例。因而 import语句合被并执行,即单例模式(Singleton模式)。
模块的整体加载,即用星号(*)指定一个对象,然后把所有需要输出的变量都加载在这个对象上。
// index_export.js let year = 2020; let month = '03'; let day = 12; let testFunc = function () { console.log(`${year}-${month}-${day}`); document.getElementById('index_import').innerHTML = `${year}-${month}-${day}`; }; export {year, month, day, testFunc};
// index_import.js import * as time from './index_export.js'; console.log(`年-月-日:${time.year}-${time.month}-${time.day}`); time.testFunc();
前面使用 import命令,在 模块导入时需要清楚的知道所要加载的变量、对象、方法...... 它们对应的 变量名、对象名、方法名......,否则无法进行加载。但实际情况是,我既想快速上手,而又不想花时间去看文档,去了解模块内的属性和方法,那咋办呢。于是就催生了 export default命令,为模块指定默认导出。
1、通过 export方式导出,在导入时要加大括号({ }),export default则不需要。
2、export default向外暴露的成员,可以使用任意变量来接收。
// index_export_default.js // 1、模块文件index_export_default.js,默认输出一个匿名函数,其他模块在加载该模块时,import命令可以为该匿名函数定义任意名称。 export default function () { console.log(`index_export_default`); }
// index_export_default.js // 2、export default命令也可以输出非匿名函数。 // testFunc函数的函数名是testFunc,但在 index_export_default模块外部这些函数的函数名是无效的,加载时,视同匿名函数加载。 export default function testFunc() { console.log(`index_export_default`); } // 或 function testFunc() { console.log(`index_export_default`); } export default testFunc;
// index_import.js // 其他模块在加载 export default命令默认输出的模块时,import命令可以为该模块定义任意名称。 import exportDefault from './index_export_default.js'; // 此时 import命令不再需要知道原模块输出的各种变量名了,同时命令后面也不需要使用大括号了。 exportDefault();
3、在一个文件或模块中,export、import可以有多个,但 export default只能有一个。
// index_export_default.js // 在一个文件或模块中,export、import可以有多个,但 export default只能有一个。 export function testFunc() { console.log(`export testFunc index_export_default`); } var func = function () { console.log(`export test_func index_export_default`); }; export { func as test_func }; export default function () { console.log(`index_export_default`); }
// index_import.js // import语句中,允许同时输入默认方法和其他接口。 import exportDefault, {testFunc, test_func} from './index_export_default.js'; testFunc(); test_func(); exportDefault();
4、export default中的 default是对应的导出接口变量。
// export default命令用于指定模块的默认输出。因此一个模块只能有一个默认输出,从而一个模块 export default命令也只能使用一次;所以,import命令后面才不用跟大括号,因为只可能唯一对应 export default命令。 // 1、本质上,export default就是输出一个名称为 default的变量或方法,所以它后面不能跟变量声明语句。 export default var tempPar = 123; // Uncaught SyntaxError: Unexpected token 'var' // 2、同样,因为 export default命令的本质是将 default后面的值,赋给 default变量,所以可以直接将值写在 export default之后。 export default 123; // 3、最后,export default就是输出一个叫做 default的变量或方法,所以系统允许开发者为 default取任意名字。 function testFunc(x, y) { return x * y; } export {testFunc as default}; // 等同于 export default testFunc;