ES6模块-团队分享
ES6模块-团队分享
大纲
- 前端模块化的历史
- ES6模块的使用
- ES6模块背后的思考
- 组件和模块
- 参考文章
前端模块化的历史
- 全局Function模式
- 命名空间模式
- IIFE模式
- IIFE模式(引入依赖)
- CommonJS
- AMD
- CMD
- ES6 模块
ES6模块的使用
什么是模块?
模块是通过不同于脚本的形式进行加载的JavaScript文件。区别在于:
- 模块代码自动运行在严格模式,并无法退出
- 模块中定义的变量只在模块的作用域中存在,不会自动添加到全局的作用域
- 模块中的
this
是undefined
- 模块中不支持HTML风格的注释
<!-- comments -->
- 模块必须要导出任何可以在模块外可用的一切
- 模块可以从其他模块导入内容
基本导出
使用export
关键字可以从模块中导出变量、函数和类:
// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// export function
export function sum(num1, num2) {
return num1 + num1;
}
// export class
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// this function is private to the module
function subtract(num1, num2) {
return num1 - num2;
}
// define a function...
function multiply(num1, num2) {
return num1 * num2;
}
// ...and then export it later
export { multiply };
导出说明:
- 每个导出的函数和类都有一个名字,除了使用
default
关键字外无法使用上面的语法导出匿名函数和类 - 查看
multiply()
函数,你可以不导出声明,支持导出引用 - 查看
substract()
函数,外部不可访问,没有显式导出的变量、函数和类对于模块是私有的
基本导入
基本格式:
import { identifier1, identifier2 } from "./example.js";
说明: 从一个模块中导入一个绑定,就类似定义一个const
的内容,也就是无法定义重名变量、导入前使用和修改值。
导入一个单一绑定
// import just one
import { sum } from "./example.js";
console.log(sum(1, 2)); // 3
sum = 1; // error
尽管example.js
中定义了多个导出,你可以只导入其中一个;为了在浏览器和Node.js中保持兼容性,尽量在文件名前使用相对路径。
导入多个绑定
// import multiple
import { sum, multiply, magicNumber } from "./example.js";
console.log(sum(1, magicNumber)); // 8
console.log(multiply(1, 2)); // 2
全部导入
特殊的应用场景支持导入整个模块作为一个对象,类似导入一个库文件,所有的模块导出均作为对象属性。
// import everything
import * as example from "./example.js";
console.log(example.sum(1,
example.magicNumber)); // 8
console.log(example.multiply(1, 2)); // 2
不管多少次使用import
对一个模块进行导入操作,模块只会执行一次;在导入模块的代码执行后,实例化的模块在保存在内存中并在其他import
语句使用时复用。
import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";
代码中example.js
只会执行一次.
模块语法不支持在语句或者函数中使用,也就是说不支持动态的导入和导出。ECMAScript dynamic-import Stage3
导入绑定的修改
对导入的内容是无法修改的,但是在模块内部可以。
模块导出:
export var name = "Nicholas";
export function setName(newName) {
name = newName;
}
模块导入:
import { name, setName } from "./example.js";
console.log(name); // "Nicholas"
setName("Greg");
console.log(name); // "Greg"
name = "Nicholas"; // error
setName
函数回到模块内执行并修改变量name
的内容,随后这个修改会反映到导入的name
这个绑定。
重命名导出和导入
有时候,你可能不想使用变量、函数、类在模块中的原始名字,你可以在导入和导出的时候使用as
关键字来指定新的名字
导出时重命名
function sum(num1, num2) {
return num1 + num2;
}
export { sum as add };
导入
import { add } from "./example.js";
导入时重命名
import { add as sum } from "./example.js";
console.log(typeof add); // "undefined"
console.log(sum(1, 2)); // 3
模块中的默认值
模块中的默认值是指使用default
指定的一个单独变量、函数或者类,每个模块中只能设定一个默认导出,指定多个默认导出会报语法错误。
导出默认值
直接导出
export default function(num1, num2) {
return num1 + num2;
}
函数可以不写名字,因为模块本身代表这个函数。
导出引用
function sum(num1, num2) {
return num1 + num2;
}
export default sum;
重命名导出
function sum(num1, num2) {
return num1 + num2;
}
export { sum as default };
导入默认值
javascript
// import the default
import sum from “./example.js”;
console.log(sum(1, 2)); // 3
1. 默认导入不需要大括号
2. 名称可以代表任意模块中的默认函数
3. 默认导入的语法是最精简的,ES6模块规范制定者希望默认值作为主流的使用场景
**具名和默认混合**
```javascript
export let color = "red";
export default function(num1, num2) {
return num1 + num2;
}
import sum, { color } from "./example.js";
console.log(sum(1, 2)); // 3
console.log(color); // "red"
默认值要在非默认值前面导入。
重命名默认值
// equivalent to previous example
import { default as sum, color } from "example";
console.log(sum(1, 2)); // 3
console.log(color); // "red"
重新导出一个绑定
几种重新导出的方式
import { sum } from "./example.js";
export { sum }
export { sum } from "./example.js";
export { sum as add } from "./example.js";
全部导出
export * from "./example.js";
这里只能导出所有的具名导出,不包括默认导出。如果要处理默认导出,你需要显式的导入后再导出。
没有绑定的导入
有些模块并没有导出任何内容,仅仅是在全局作用域中做了些修改;虽然模块无法直接修改全局作用域的变量、函数和类,但是对于內建对象的修改可以反映到其他模块中。
// module code without exports or imports
Array.prototype.pushAll = function(items) {
// items must be an array
if (!Array.isArray(items)) {
throw new TypeError("Argument must be an array.");
}
// use built-in push() and spread operator
return this.push(...items);
};
这个模块对Array
原型增加一个pushAll
方法.
import "./example.js";
let colors = ["red", "green", "blue"];
let items = [];
items.pushAll(colors);
这段代码导入和执行模块代码,添加pushAll
方法到array原型中。
没有绑定的导入通常用于创建polyfill或者shim。
加载ES6 模块
-
使用打包工具和Babel转译为ES5
-
使用
script
标签,并设置type
为module
(IE11不兼容,使用前查看兼容性)
默认使用defer
属性,按照代码出现的顺序进行加载,执行需要在文档解析完成后,执行顺序也是按照代码出现的顺序。
<!-- this will execute first -->
<script type="module" src="module1.js"></script>
<!-- this will execute second -->
<script type="module">
import { sum } from "./example.js";
let result = sum(1, 2);
</script>
<!-- this will execute third -->
<script type="module" src="module2.js"></script>
如果模块代码中又引入其他的模块,执行的顺序会变得复杂一些:
加载顺序:
- Download and parse module1.js.
- Recursively download and parse import resources in module1.js.
- Parse the inline module.
- Recursively download and parse import resources in the inline module.
- Download and parse module2.js.
- Recursively download and parse import resources in module2.js
执行顺序:
- Recursively execute import resources for module1.js.
- Execute module1.js.
- Recursively execute import resources for the inline module.
- Execute the inline module.
- Recursively execute import resources for module2.js.
- Execute module2.js.
- web worker
// load module.js as a module
let worker = new Worker("module.js", { type: "module" });
ES6模块背后的思考
对比CommonJS和AMD
CommonJS
在Node.js中的主要实现方式,特性如下:
- 紧凑的语法
- 同步加载
- 主要使用: 服务端
Asynchronous Module Definition(AMD)
主要的实现是RequireJS
,特性如下:
- 稍微复杂的语法
- 异步加载
- 主要使用: 浏览器
ES6模块
目标:
- 偏爱默认导出
- 静态模块结构
- 支持同步和异步加载
- 支持模块间的循环依赖
优势:
- 比CommonJS更紧凑的语法
- 结构可以被静态分析(静态检测、优化等)
- 对循环引用比CommonJS更好
- 支持异步加载
八卦: ES6 Module的主要设计者Dave Herman和Sam Tobin Hochstadt。
组件和模块
- 模块为基础,组件基于模块
- 组件侧重UI封装,针对,包括html,css,js和image;模块侧重数据、功能封装
组件化参考张云龙-前端工程