书格前端

ES6中的模块化


ES6中的模块化

这里主要是针对ES6 Module技术出现的讨论,对比历史的一些模块化解决方案。

背景、初衷、目标

背景

简单的讲,随着前端应用的日益庞大和复杂,对于代码的拆分复用要求更高,也就出现了对代码模块化的需求。

在ES6 Module出现前,社区中出现的两个方案CommonJS和AMD:

CommonJS

在Node.js中的主要实现方式,特性如下:

Asynchronous Module Definition(AMD)

主要的实现是RequireJS,特性如下:

这里提一句,ES6 Module的主要设计者Dave Herman 和 Sam Tobin Hochstadt。

初衷

这里讲的ES6 Module的设计初衷,在ES6 Module出现前社区已经在模块化上有一些令人印象深刻的变通方案,以CommonJS和AMD为代表,这两者各有优缺点并且不兼容,因此ES6 Module的设计初衷是为了吸取这两者的优点实现ECMAScript的标准。

目标

ES6 Module的目标是出一个让CommonJS和AMD社区都能接受的方案:

优势和劣势(trade-off)

ES6 Module实际达成的方案特性如下:

适用的场景(业务场景、技术场景)

适用于浏览器端和服务端(Nodejs),目前(2019年)主流的浏览器大部分都不支持ES6 Module的特性,因此使用的话还得使用Babel来转译。

组成部分和关键点

ES6 Module分两部分:

使用场景

在浏览器侧的使用

使用<script>进行加载

通过type属性值为module来标识模块进行加载,支持外部文件和内联的方式,默认使用defer的行为,也可以指定async

<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>

<!-- include a module inline -->
<script type="module">

import { sum } from "./example.js";

let result = sum(1, 2);

</script>

通过Worker进行加载

// load module.js as a module
let worker = new Worker("module.js", { type: "module" });

通过打包工具进行打包

使用webpack或者browserify等打包工具进行打包后使用。

声明式语法

声明式语法分为两种类型:具名导出(每个模块有多个)和默认导出(每个模块一个)。

具名导出(named exports)

通过关键字export前缀可以导出多个内容,并有名称来进行区分。

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

默认导出(default exports)

一个模块只有一个默认导出,并且默认导出尤其容易进行导入。

//------ myFunc.js ------
export default function () { ... };

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();

对于class的使用

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

//------ main2.js ------
import MyClass from 'MyClass';
let inst = new MyClass();

默认导出其实可以理解为特殊的具名导出

导入:

import { default as foo } from 'lib';
import foo from 'lib';

导出:

//------ module1.js ------
export default 123;

//------ module2.js ------
const D = 123;
export { D as default };

答疑

The answer is that you can’t enforce a static structure via objects and lose all of the associated advantages (described in the next section).

如果通过对象进行导出会丢失静态结构,从而失去原先的优势。

编程式语法

针对ES6 Module的编程式实现,目前规范处于stage 3。

使用编程式语法,可以做到:

<!DOCTYPE html>
<nav>
  <a href="books.html" data-entry-module="books">Books</a>
  <a href="movies.html" data-entry-module="movies">Movies</a>
  <a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>

<main>Content will load here!</main>

<script>
  const main = document.querySelector("main");
  for (const link of document.querySelectorAll("nav > a")) {
    link.addEventListener("click", e => {
      e.preventDefault();

      import(`./section-modules/${link.dataset.entryModule}.js`)
        .then(module => {
          module.loadPageInto(main);
        })
        .catch(err => {
          main.textContent = err.message;
        });
    });
  }
</script>

已有的实现和它之间的对比

有关模块化的进程,前端模块化详解一句讲解得比较详细了,可以参考。

参考文档