JavaScript的模块机制
伴随着前端应用的复杂化, 将程序按照模块进行解耦和拆分逐渐成为了常态。 而JavaScript直到ES6才正式引入模块机制, 在这之前, 社区中涌现了CommonJS、AMD等多种模块化解决方案。
在JavaScript的语法规则中,脚本和模块是两种不同的概念:
脚本是由宿主环境引入并主动执行的JavaScript代码段,而模块则是在JavaScript代码中通过import引入进来,等待被调用的库.
相对于脚本而言,模块机制主要有语句,import
声明和export
声明组成。 但是不同规范和方案下的模块导入导出规则太难记了,本文就对ES6和CommonJS相应语法规则进行了梳理和总结。
ES6 Moldue语法
- import 声明
ES6的import
声明有两种使用方式:
import "moduleA"; // 引入moduleA模块, 确保moduleA模块中代码被执行
import a from "moduleA"; // 把模块moduleA默认的导出值放入当前变量 a
我们经常使用的是第二种带from关键字的,它有很多灵活的使用语法:
import a from "moduleA"; // 引入moduleA中声明的默认导出对象
import {b,c,d} from "moduleA"; // 引入moduleA中所导出的变量
import {b as bbb,c,d} from "moduleA";
import * as x from "moduleA"; // 将moduleA中所有导出的变量都放入当前x对象中
- export 声明
export
命令声明了模块对外暴露的接口, 主要有三种使用方式:
// export要导出的变量列表
let a=1;
let b=2;
let c=3;
export {a, b, c}
// 直接在声明语句之前使用
export let a=1;
export function f(){}
// 默认导出
let a={}
export default a
这里,export default
的语义是,将a对象的值赋给一个名为default
的变量并导出,以便引用方无需知道变量名就可以直接引入并使用:
import bbb from "moduleA"; // 引入moduleA中声明的默认导出对象
这里有一点需要特别注意一下,export default
导出的是一个变量值,后续模块中的变化和该值没有关系;而export
导出的是变量本身,因此还会受到模块内部的控制和影响。
CommonJS语法
CommonJS
是一种模块规范,而Node使用了CommonJS规范来对模块进行管理。
- 导出
在CommonJS的模块导出语法有两种:
// 整体导出
let a={}
module.exports=a;// 导出对象a
// 导出多个变量
exports.b=1;
exports.b=2;
这里,module.exports
和exports
指向了同一个引用对象(var exports = module.exports),都指向了最终模块所导出的对象。
需要注意的是,由于模块最终导出的对象一定是module.exports
所指向的对象,因此我们只能向exports上挂载变量,而不要去改变exports
指向的对象。
- 导入
CommonJS的模块导出相对来说比较简单:
let b=require("a") from 'moduleA'// 加载并读取moduleA所声明的module.exports对象
在Node中,require
命令会读入并执行一个JavaScript
模块文件,并根据该模块中所声明的module.exports
在内存中创建一个导出对象(原对象的copy)。
对比
虽然CommonJS和ES6在语法颇为相似,但是在模块加载机制上却相去甚远。
我们上面提到,CommonJS在首次require一个模块的时候,会执行该模块代码并将所声明的module.exports
对象缓存起来,以便下次使用。同时,这里所缓存的对象是原模块对象中的拷贝。
而ES6的import只是在编译阶段使用了一个引用来保存被导出对象,当代码执行期间才会根据引用地址去读取对应的值。因此,在外部对于导出变量的修改,可以影响到被引用模块内部。