在ES6之前,Javascript并没有官方标准的模块化方案,在社区中就出现了Common.js和AMD这两种方案,前者主要用于服务端,后者用在浏览器端,为了统一写法,又出现了UMD的标准,在ES6中,Javascript终于有了官方标准的模块化方案,这篇博客我们就来看看ES6的模块化方案。
// ------lib.js-----
export function foo() {
console.log('foo in lib.js');
}
export default function bar() {
console.log('bar in lib.js')
}
// ------main.js-----
import { foo } from './lib.js';
import bar from './lib.js';如上,是ES6中Module的用法,这里有两个保留字import和export,从上面的代码中,我们可以发现,对于不同的export方式,对应的import的方式也不一样。
// ------lib.js-----
let a = 1;
export foo() {}
export bar() {}
export a;如上,就是name export的写法,每一个函数或者变量,直接被export,这种每写一个函数或者变量就export的方式也叫做inline export,如果在一个Module中有很多需要export的东西,这种写法就有些累赘了,这时候可以写成这样:
// ------lib.js------
let a = 1;
function foo() {}
function bar() {}
export {
foo,
bar,
a
}在ES6 Module中,有local name和export name的概念,顾名思义local name就是在Module中的名字,export name就是模块暴露给外界的名字。
// ------lib.js------
let a = 1;
function foo() {} // {A}
function bar() {}
export {
foo as bar, // {B}
bar as foo,
a as b,
}在上面的代码中,{A}行的foo是local name,在{B}行的bar是export name,同样的我们在import的时候也可以使用别名。
// ------lib.js------
export function foo() {}
// ------main.js------
import { foo as bar } from './lib.js';
bar();// ------lib.js------
export default foo() {}
// ------main.js------
import foo from 'lib.js';
foo();如上,我们import了一个default的export,default export和name export在import的时候,name export需要加大括号,而default export则不需要。
// ------lib.js------
let a = 1;
function foo() {
console.log('foo in lib.js');
}
function bar() {
console.log('bar in lib.js')
}
export {
foo,
bar,
a,
}
// ------middleLib.js------
export { foo as bar, bar as foo } from './lib';
// ------main.js------
import { foo, bar } from './middleLib';
foo();
bar();如上我们将模块lib.js中暴露的export通过另一个模块暴露出去了,这种常见的用法是,在封装好一个大的模块后,这个大的模块只需要向外界暴露部分小模块的API:
- module
-- a.js
-- b.js
-- index.js如上,在module这个folder下我们有a.js, b.js, index.js,a.js和b.js是一些功能的实现,我们将a.js, b.js中需要向外界暴露的API通过index.js转发。
ES6给我们提供了很简洁的语法去使用模块,但是ES Module简洁的外表下,背后的细节仍然值得我们注意。
如何理解ES Module是静态的这句话呢?意思就是,ES Module不同于Common.js这种模块化方案,只有在运行时才可以确定依赖的模块,在源码中看到的依赖关系就是运行时的依赖。
//------commonJSModule.js------
function foo() {}
function bar() {}
module.exports = {
foo,
bar,
}
//------main.js------
if (something) {
let foo = require('./commonJSModule.js').foo;
foo();
} else {
let foo = require('./commonJSModule.js').bar;
bar();
}如上,在CommonJS中这中动态的在运行时决定依赖的方式在ES Module中是行不通的(但是有提案在做运行时的loader —> https://github.com/whatwg/loader/)。
// ------lib.js------
let a = 1;
function foo() {
console.log('foo in lib.js');
}
function bar() {
console.log('bar in lib.js')
}
export {
foo,
bar,
a,
}
// ------main.js------
foo();
bar();
import { foo, bar } from './lib';如上,我们可以先调用export的函数,在调用之后再import,这个在编译的时候会将import提升到顶层,但是在实际开发的过程中,虽然可以这样,但是这并不是好的代码风格。
// ------commonJSModule.js------
function foo(){}
let a = 0;
module.exports = {
foo,
a,
}
// ------main.js------
let a = require('./lib.js').a;
++a; // 1
console.log(require('./lib.js').a) // 0上面的代码在CommonJS下,我们可以修改require的东西,因为在commonJS中是拷贝一份,但是在ES Module中,我们无法修改import的东西,import的模块在行为上类似于const变量和frozen object
// ------lib.js------
let a = 1;
function foo() {
console.log('foo in lib.js');
}
function bar() {
console.log('bar in lib.js')
}
export {
foo,
bar,
a,
}
// ------main.js------
import { foo, bar, a } from './lib';
foo();
bar();
foo.a = 1; // works {A}
++a; // error {B}ES Module的静态结构的设计特点,让代码在编译的时候就能确定依赖关系,不同于运行时,只有代码跑起来的时候很多东西才能确定,就好比,你计划完成一项复杂的项目,静态的结构能保证你在做之前,你确定的事是不会变的,而不是只要等到在项目进行中去确定,面对可预测的问题,我们是好解决的。
dead code elimination,减小bundle文件的大小(RollUp基于ES Module实现了tree shaking)lint工具的检测javascript支持宏做准备(宏操作需要静态的结构 —> https://www.sweetjs.org/)兴趣遍地都是,坚持和持之以恒才是稀缺的