felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

ES6 模块(Module) #50

Open felix-cao opened 6 years ago

felix-cao commented 6 years ago

JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 RubyrequirePythonimport,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJSAMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJSAMD 规范,成为浏览器和服务器通用的模块解决方案。 CommonJS 模块请移步 《CommonJS 的模块规范》,

ES6 模块的设计思想是静态化,即“编译时加载“或“静态加载“; 静态化的目的是使得编译时就能确定模块的依赖关系,以及输入和输出的变量: 🔢

一、模块导出的写法

1.1、写法一: 直接 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 和 两个函数声明 squareaddexport 是在每个变量声明或函数声明前使用的。

1.2、写法二: export 列表

但是上面的写法不简洁,每个要输出的部分前都写上 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));

1.3、写法三:export default 默认输出

export default 一般用于输出单个值,export default 是在 export 的基础上,为规定模块提供一个默认的单一的对外接口。

从前面的例子可以看出,使用 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 导入,默认输出时以文件名作为默认变量

本质上,export 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.

正是因为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 模块为例。

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));

三、模块的重命名 as

如果导入的变量名恰好和你模块中的变量名冲突了,ES6 允许你给你导入的东西重命名:

import {add as addNum} from "./add";
addNum(10,9);

类似地,你在导出变量的时候也能重命名。这个特性在你想将同一个变量名导出两次的场景下十分方便,举个栗子:

function add() {}
export {
    add as addNum,
    add as addInt
};

四、import 与 export 复合写法

如果在一个模块之中,先输入后输出同一个模块,import 语句可以与 export 语句写在一起。

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

上面代码中,exportimport 语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foobar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foobar

五、有趣的静态和动态

随着技术发展,现今 JavaScript 作为一门动态语言已经得到了一个令人惊讶的静态模块系统。

Reference