ES6 新特性之 Module 模块

概述

ES6 之前,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种,而 ES6 在语言标准的层面上,实现了模块功能,可以取代 CommonJS 和 AMD 规范,成为通用的模块解决方案

模块功能主要由两个命令构成:exportimport

// module.js
let firstName = 'John'
let lastName = 'Han'
export { firstName, lastName }

// index.js
import { firstName, lastName } from './module.js'
console.log(firstName + ' ' + lastName) // John Han

export 命令

一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取

如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量

export let firstName = 'John'
export let lastName = 'Han'

export 的写法还有另外一种

let firstName = 'John'
let lastName = 'Han'
export { firstName, lastName }

应该优先考虑使用这种写法,因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量

export 命令具有提升效果,只要处于非块级作用域内就行

export { firstName, lastName }
let firstName = 'John'
let lastName = 'Han'

export 命令除了输出变量,还可以输出函数或类(class)

export function multiply (x, y) {
  return x * y
}

export class Myclass {
    // ...
}

通常情况下,export 输出的变量就是本来的名字,但是可以使用 as 关键字重命名

function f1() { ... }
function f2() { ... }

export {
  f1 as streamF1,
  f2 as streamF,
  f2 as streamLatestVersion
}

上面代码使用 as 关键字,重命名了函数 f1 和 f2 的对外接口,重命名后,f2 可以用不同的名字输出两次

需要特别注意的是,export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系

// 报错
export 1
// 报错
let m = 1
export m
// 报错
function f () {}
export f

面两种写法都会报错,因为没有提供对外的接口,正确的写法如下

// 写法一
export let m = 1
// 写法二
let m = 1
export { m }

// 写法一
export function f () {}
// 写法二
function f () {}
export { f }

export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值

export let foo = 'bar'
setTimeout(() => foo = 'baz', 1000)

上面代码输出变量 foo,值为 bar,1 秒之后变成 baz

import 命令

使用 export 命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import 命令加载这个模块

import { firstName, lastName } from './module.js'
console.log(firstName + ' ' + lastName) // John Han

import 命令接受一对大括号,里面指定要从其他模块导入的变量名,大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同

如果想为输入的变量重新取一个名字,import 命令要使用 as 关键字,将输入的变量重命名

import { lastName as surname } from './module.js'

import 后面的 from 指定模块文件的位置,可以是相对路径和绝对路径,.js 后缀可以省略

如果只是模块名,不带有路径,则打包工具会先查找指定目录(如 node_modules),找到入口文件(入口文件一般在 package.json 中的 main 配置项上面配置)

import { Table, Form } from 'element-ui'

import 命令和 export 命令一样具有提升效果,会提升到整个模块的头部

foo()
import { foo } from 'my_module'

import 语句会执行所加载的模块,因此可以有下面的写法

import 'lodash'

模块的整体加载

除了指定加载某个输出值,还可以使用整体加载

先看看逐一加载的例子

// module.js
let firstName = 'John'
let lastName = 'Han'
export { firstName, lastName }

// index.js
import { firstName, lastName } from './module.js'
console.log(firstName + ' ' + lastName) // John Han

再看看整体加载的例子

// module.js
let firstName = 'John'
let lastName = 'Han'
export { firstName, lastName }

// index.js
import * as user from './module.js'
console.log(user.firstName + ' ' + user.lastName) // John Han

整体加载相当于用星号(*)指定一个对象(上例的 user),所有输出值都加载在这个对象上面

export default 命令

有些时候,用户不知道或不想知道所要加载的变量名或函数名,为了方便用户,export default 命令提供了一种可行的方式

// module.js
let firstName = 'John'
export default firstName

// index.js
import fn from './module.js'
console.log(fn) // John

上面代码的 import 命令,可以用任意名称指向 module.js 输出的变量,这时就不需要知道原模块输出的变量名
需要注意的是,这时 import 命令后面,不使用大括号

下面比较一下默认输出和正常输出

// 第一组
export default function crc32 () { // 输出
  // ...
}
import crc32 from 'crc32' // 输入

// 第二组
export function crc32 () { // 输出
  // ...
}
import { crc32 } from 'crc32' // 输入

本质上,export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字,所以,下面的写法是有效的

// modules.js
function add (x, y) {
  return x * y
}
export { add as default }
// 等同于
// export default add

// index.js
import { default as foo } from 'modules'
// 等同于
// import foo from 'modules'

正是因为 export default 命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句

// 正确
export let a = 1
// 正确
let a = 1
export default a

// 错误
export default let a = 1

同样地,因为 export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后

// 正确
export default 42

// 报错
export 42

如果想在一条 import 语句中,同时输入默认方法和其他接口,可以写成下面这样

import _, { each } from 'lodash'

对应上面代码的export语句如下

export default function (obj) {
  // ···
}
export function each (obj, iterator, context) {
  // ···
}

export default 也可以用来输出类

// MyClass.js
export default class { ... }

// index.js
import MyClass from 'MyClass'
let o = new MyClass()

export 与 import 的复合写法

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

export { foo, bar } from 'my_module'

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

模块的接口改名和整体输出,也可以采用这种写法

// 接口改名
export { foo as myFoo } from 'my_module'

// 整体输出
export * from 'my_module'

默认接口的写法如下

export { default } from 'foo'

具名接口改为默认接口的写法如下

export { es6 as default } from './someModule'

// 等同于
import { es6 } from './someModule'
export default es6

同样地,默认接口也可以改名为具名接口

export { default as es6 } from './someModule'

模块的继承

有一个 circle 模块如下

// circle.js
export let r = 5
export function getName () {}

再新建一个 circleplus 模块如下

// circleplus.js
export * from 'circle'
export let e = 2.71828182846
export default function (x) {
  return Math.exp(x)
}

上面代码中的 export *,表示先输入再输出 circle 模块的所有属性和方法,然后继续输出了自定义的 e 变量和默认方法,这就实现了 circleplus 模块继承 circle 模块的属性和方法的功能

注意:export * 命令会忽略 circle 模块的 default 方法

本文转载自 http://es6.ruanyifeng.com/#docs/module 并进行整理

除特殊说明外本人博客均属原创,转载请注明出处:http://blog.johnhan.cn/blog_1050.html
鄂ICP备17018604号-1  鄂公网安备42060702000030号