在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/)兴趣遍地都是,坚持和持之以恒才是稀缺的