模块化代码-所有代码都可以按需彼此访问并高效加载。

基础知识

每一个 ES6 模块都是一个包含 JS 代码的文件,模块本质上就是一段脚本,而不是用 module 关键字定义一个模块,但是模块与脚本还是有两点区别:

  • 在模块中你可以使用 import 和 export 关键字。
  • 在 ES6 模块中,无论你是否加入“use strict;”语句,默认情况下模块都是在严格模式下运行。
     

导出模块到其他模块中最简单的是使用export:

// kittydar.js - 找到一幅图像中所有猫的位置
// (事实上是 Heather Arthur 写的这个库)
// (但是她没有使用 ES6 中新的模块特性,因为那时候是 2013 年)
export function detectCats(canvas, options) {
var kittydar = new Kittydar(options);
return kittydar.detectCats(canvas);
}
export class Kittydar {
... 处理图片的几种方法 ...
}
// 这个 helper 函数没有被 export。
function resizeCanvas() {
...
}
...

        可以导出所有的最外层函数、 类以及 var、 let 或 const 声明的变量。代码就是模块,不是一段脚本,所以所有的声明都被限定在模块的作用域中,对所有脚本和模块全局不可见。你需要做的是将组成模块公共 API 的声明全部导出。
        在模块中,除 export 之外的代码无异于普通代码,你可以访问类似 Object 和 Array这样的全局对象。

引用模块 import

// demo.js - Kittydar 的 demo 程序
import {detectCats} from "kittydar.js";
function go() {
var canvas = document.getElementById("catpix");
var cats = detectCats(canvas);
drawRectangles(canvas, cats);
}

导入多个模块:

import {detectCats, Kittydar} from "kittydar.js";

        运行的模块中包含一条 import 声明时,首先会加载被导入的模块;然后依赖图的深度优先遍历按顺序执行每一个模块的主体代码;为了避免形成回环,所有已执行的模块都会被忽略。

Export列表

在花括号中按照列表的格式写下你想导出的所有名称

export {detectCats, Kittydar};
// 此处不需要 `export`关键字
function detectCats(canvas, options) { ... }
class Kittydar { ... }

        export 列表可以在模块文件最外层作用域的每一处声明,不一定非要把它放在模块文件的首行。你也可以声明多个 export 列表,甚至通过其它的 export 声明打造一个混合的 export 列表,只要保证每一个被导出的名称是唯一的即可。

重命名 import 和 export

 若导出的名字与其他名称冲突,可以使用重命名解决问题

// suburbia.js
// 这两个模块都会导出以`flip`命名的东西。
// 要同时导入两者,我们至少要将其中一个的名称改掉。
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";
...

导出的时候也可以重命名

// unlicensed_nuclear_accelerator.js - 无 DRM(数字版权管理)的媒体流
// (这不是一个真实存在的库,但是或许它应该被做成一个库)
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};

Default Exports

    目前使用的模块系统有Common.js 和AMD.js .ES6支持与这两个模块的交互,如果有Node项目,并且已经执行 npm install lodash 可以从lodash中导入独立的函数

import {each, map} from "lodash";
each([3, 2, 1], x => console.log(x));

另一种方式导入:_each.();用 require()加载这些模块也会得到相同的结果——exports 对象。

import _ from "lodash";
//eqauls
import {default as _} from "lodash";

同时ES6不止可以导出CommonJS的包,也可以导出想要的内容:例如导出非ES6包的colors它是诸多CommonJS模块的集合

// `var colors = require("colors/safe");`的 ES6 等效代码
import colors from "colors/safe";

默认导出与其他导出类似,唯一不用就是它名叫“default”

let myObject = {
field1: value1,
field2: value2
};
export {myObject as default};

//简略表达
export default {
field1: value1,
field2: value2
};

模块对象

import * as cows from "cows";

        import *时,导入的其实是一个模块命名空间对象,模块将它的所有属性都导出了。所以如果“cows”模块导出一个名为 moon()的函数,然后用上面这种方法“cows”将其全部导入后,你就可以这样调用函数了: cows.moo()。VUE中使用这样的用法

JavaScript学习之ES6 ES2015学记笔记(十五)-模块(MODULES)_ES6

聚合模块

有时候为了简化代码,可以用统一的方式将其他模块中的内容聚合在一起导出,

// world-foods.js - 来自世界各地的好东西
// 导入"sri-lanka"并将它导出的内容的一部分重新导出
export {Tea, Cinnamon} from "sri-lanka";
// 导入"equatorial-guinea"并将它导出的内容的一部分重新导出
export {Coffee, Cocoa} from "equatorial-guinea";
// 导入"singapore"并将它导出的内容全部导出
export * from "singapore";

使用export * 方法容易与其他命名冲突,使用时需要注意。

Import实际做了什么

JS引擎加载模块会按照以下步骤执行:

  • 语法解析:阅读模块源代码,检查语法错误。
  • 加载:递归地加载所有被导入的模块。这也正是没被标准化的部分。
  • 连接:每遇到一个新加载的模块,为其创建作用域并将模块内声明的所有绑定填充到该作用域中,其中包括由其它模块导入的内容。
  • 运行时:最终,在每一个新加载的模块体内执行所有语句。此时,导入的过程就已经结束了,所以当执行到达有一行 import 声明的代码的时候……什么都没发生!

       ES6 系统实现为:在编译时计算所有依赖并将所有模块打包成一个文件,通过网络一次传输所有模块!像 webpack 这样的工具就实现了这个功能。

静态 vs 动态:论规则及破例之法

  • JavaScript 作为一门动态语言已经得到了一个令人惊讶的静态模块系统,在模块的最外层作用域使用 import 和 export,不可在条件语句中使用,也不能在函数作用域中使用 import。
  • 所有导出的标识符一定要在源代码中明确地导出它们的名称。
  • 一个模块的所有依赖必须在模块代码运行前完全加载、解析并且及早连接,不存在一种通过 import 来按需懒加载的语法
  • import 模块产生的错误没有错误恢复机制。一旦有一个模块无法加载或连接,所有的模块都不会运行,而且你不能在 try/catch 代码块中捕捉 import 的错误信息。webpack在编译时会检查这些错误。
  • 不支持在模块加载依赖前运行其它代码的钩子,这也意味着无法控制模块的依赖加载过程。
  • ES6 模块语法非常静态
     

 

本文参考《ES6-In-Depth》