Open felix-cao opened 6 years ago
JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
JavaScript
Ruby
require
Python
import
CSS
@import
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。 CommonJS 模块请移步 《CommonJS 的模块规范》,
ES6
CommonJS
AMD
ES6 模块的设计思想是静态化,即“编译时加载“或“静态加载“; 静态化的目的是使得编译时就能确定模块的依赖关系,以及输入和输出的变量: 🔢
use strict
export
在每个变量、函数或类前面加上关键字 export。
// lib.ts export const helloWorld = 'Hello World!'; export function add(x, y) { return x + y; } export class Person { constructor(name) { this.name = name } getName() { return this.name } } // feature.ts, 在这个文件中 import 导入 import {helloWorld , add, Person} from 'lib'; const person = new Person('Felix'); console.log(helloWorld); console.log(add(10,15)); console.log(person.getName());
上面的代码在 lib.ts 文件里导出了一个变量声明 sqrt 和 两个函数声明 square 和 add, export 是在每个变量声明或函数声明前使用的。
lib.ts
sqrt
square
add
但是上面的写法不简洁,每个要输出的部分前都写上 export 很麻烦,所以 ES6 又给我们提供了一个简洁优雅的写法:只写一行你想要输出的变量列表,再用花括号包起来。
// lib.ts const sqrt = Math.sqrt; function square(x) { return x * x; } function add(x, y) { return x + y; } export {sqrt, square, add}; // export 导出的另外一个写法(推荐) // feature.ts, 在这个文件中 import 导入 import {sqrt, square} from 'lib'; console.log(square(8)); console.log(add(10,15));
import { sqrt, square } from 'lib’
as
export default 一般用于输出单个值,export default 是在 export 的基础上,为规定模块提供一个默认的单一的对外接口。
export default
从前面的例子可以看出,使用 import 命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到 export default 命令,为模块指定默认输出。
// add.ts export default function (x,y){ return x + y; } // feature.ts import add from './add' add(10,9);
上面的代码在 add.ts 文件里导出一个匿名函数,在函数前面使用了 export default, 在 feature.ts 文件里使用 Import 导入,默认输出时以文件名作为默认变量
add.ts
feature.ts
Import
本质上,export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
default
// modules.js function add(x, y) { return x * y; } export {add as default}; // 等同于 // export default add; // app.js import { default as foo } from 'modules'; // 等同于 // import foo from 'modules';
上面的代码在 modules.js 文件中,导出 add 函数并重命名为 default,在 app.js 中导入这个 default 并重命名为 foo.
modules.js
app.js
foo
正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。它后面不能跟变量声明语句。
// 正确 var a = 1; export default a; // 错误, export default 后面跟的是变量声明表达式 export default var a = 1;
👍 export default 命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此 export default 命令只能使用一次。所以,import 命令后面才不用加大括号,因为只可能唯一对应 export default 命令。
有了export default命令,输入模块时就非常直观了,以输入 lodash 模块为例。
lodash
import _ from 'lodash';
👍 如果想在一条 import 语句中,同时输入默认方法和其他接口,可以写成下面这样。
import _, { each, forEach } from 'lodash';
除了指定加载某个输出值,还可以使用整体加载,即用 *星号()** 指定一个对象,所有输出值都加载在这个对象里
// lib.ts const sqrt = Math.sqrt; function square(x) { return x * x; } function add(x, y) { return x + y; } export {sqrt, square, add}; // feature.ts import * as lib from 'lib'; console.log(lib.square(8)); console.log(lib.add(10,15));
如果导入的变量名恰好和你模块中的变量名冲突了,ES6 允许你给你导入的东西重命名:
import {add as addNum} from "./add"; addNum(10,9);
类似地,你在导出变量的时候也能重命名。这个特性在你想将同一个变量名导出两次的场景下十分方便,举个栗子:
function add() {} export { add as addNum, add as addInt };
如果在一个模块之中,先输入后输出同一个模块,import 语句可以与 export 语句写在一起。
export { foo, bar } from 'my_module'; // 可以简单理解为 import { foo, bar } from 'my_module'; export { foo, bar };
上面代码中,export 和 import 语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。
bar
随着技术发展,现今 JavaScript 作为一门动态语言已经得到了一个令人惊讶的静态模块系统。
hack
polyfill
app
try/catch
webpack
JavaScript
一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby
的require
、Python
的import
,甚至就连CSS
都有@import
,但是JavaScript
任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。在
ES6
之前,社区制定了一些模块加载方案,最主要的有CommonJS
和AMD
两种。前者用于服务器,后者用于浏览器。ES6
在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代CommonJS
和AMD
规范,成为浏览器和服务器通用的模块解决方案。CommonJS
模块请移步 《CommonJS 的模块规范》,ES6
模块的设计思想是静态化,即“编译时加载“或“静态加载“; 静态化的目的是使得编译时就能确定模块的依赖关系,以及输入和输出的变量: 🔢ES6
的模块自动采用严格模式,不管你有没有在模块头部加上use strict
。export
声明需要导出的变量、函数或类import
声明从其他模块导入变量、函数或类。import
和export
命令只能在模块的顶层一、模块导出的写法
1.1、写法一: 直接 export
在每个变量、函数或类前面加上关键字 export。
上面的代码在
lib.ts
文件里导出了一个变量声明sqrt
和 两个函数声明square
和add
,export
是在每个变量声明或函数声明前使用的。1.2、写法二: export 列表
但是上面的写法不简洁,每个要输出的部分前都写上
export
很麻烦,所以ES6
又给我们提供了一个简洁优雅的写法:只写一行你想要输出的变量列表,再用花括号包起来。import
命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(lib.ts)对外接口的名称相同。 👍import { sqrt, square } from 'lib’
; 是import
的特有语法,并不是析构(destructing) 语法import
命令要使用as
关键字,将输入的变量重命名。1.3、写法三:export default 默认输出
export default
一般用于输出单个值,export default
是在export
的基础上,为规定模块提供一个默认的单一的对外接口。从前面的例子可以看出,使用
import
命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到
export default
命令,为模块指定默认输出。上面的代码在
add.ts
文件里导出一个匿名函数,在函数前面使用了export default
, 在feature.ts
文件里使用Import
导入,默认输出时以文件名作为默认变量import
命令可以为该匿名函数指定任意名字。import
命令后面,不使用大括号。 👍本质上,
export default
就是输出一个叫做default
的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。上面的代码在
modules.js
文件中,导出add
函数并重命名为default
,在app.js
中导入这个default
并重命名为foo
.正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。它后面不能跟变量声明语句。
👍
export default
命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default
命令只能使用一次。所以,import
命令后面才不用加大括号,因为只可能唯一对应export default
命令。有了
export default
命令,输入模块时就非常直观了,以输入lodash
模块为例。👍 如果想在一条
import
语句中,同时输入默认方法和其他接口,可以写成下面这样。二、导入之模块的整体加载 *
除了指定加载某个输出值,还可以使用整体加载,即用 *星号()** 指定一个对象,所有输出值都加载在这个对象里
三、模块的重命名 as
如果导入的变量名恰好和你模块中的变量名冲突了,ES6 允许你给你导入的东西重命名:
类似地,你在导出变量的时候也能重命名。这个特性在你想将同一个变量名导出两次的场景下十分方便,举个栗子:
四、import 与 export 复合写法
如果在一个模块之中,先输入后输出同一个模块,
import
语句可以与export
语句写在一起。上面代码中,
export
和import
语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo
和bar
实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo
和bar
。五、有趣的静态和动态
随着技术发展,现今
JavaScript
作为一门动态语言已经得到了一个令人惊讶的静态模块系统。import
和export
,不可在条件语句中使用,也不能在函数作用域中使用import
。hack
模块对象并为其添加polyfill
风格的新特性。import
来按需懒加载的语法。import
模块产生的错误没有错误恢复机制。一个app
可能囊括了上百个模块,一旦有一个模块无法加载或连接,所有的模块都不会运行,而且你不能在try/catch
代码块中捕捉import
的错误信息。(上面这些描述的本意是说:系统是静态的,webpack
可在编译时为你检测那些错误。)Reference
ES6
的一系列新特性的介绍。)