[关闭]
@frank-shaw 2017-02-15T07:14:42.000000Z 字数 3145 阅读 2180

对JavaScript模块化的理解以及webpack在其中的作用

javaScript


写在前面

模块化的趋势:

  1. JavaScript被越来越广泛地应用
  2. 网页正在朝着单页应用的方向发展
  3. 代码量比之前更加庞大

因为上面的几个原因,导致了我们需要对我们的代码有一个更好的组织形式。模块系统就提供了一种选择:可以将你的一大坨代码分装为多个不同的模块。

模块系统的形式

有很多不同的模块系统形式,我们来看看概览:

使用script标签的形式

如果你不使用模块系统,那么通常在index.html中就需要写下类似下面的代码:

  1. <script src="module1.js"></script>
  2. <script src="module2.js"></script>
  3. <script src="libraryA.js"></script>
  4. <script src="module3.js"></script>

这个就是最原始的JavaScript文件加载方式。如果把每个文件都看成是一个模块,那么它们的接口如果依照上述方式来布置的话,那么就会暴露在全局作用域下,也就是定义在window对象中。我们从JavaScript的学习中可以知道:除非必要,否则不要在全局作用域中定义变量或对象。

这种原始的加载方式的弊端:

CommonJS中的模块规范

我们先来了解:为什么会有CommonJS规范?
我们知道组成JavaScript的重要规范(包括ECMAScript、DOM、BOM等)都是针对web浏览器环境的。并没有合适的针对在服务器环境中使用JavaScript的规范。伴随着NodeJS这一类使用JavaScript来开发服务器的语言的火热,那么就需要有这样一种针对服务器环境的使用JavaScript的规范。CommonJS 是以在浏览器环境之外构建 JavaScript生态系统为目标而产生的项目,比如在服务器和桌面环境中。

那么我们也大概能够知道:CommonJS中的模块规范主要是在服务器端使用模块化。该规范的核心思想是允许模块通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或 module.exports 来导出需要暴露的接口。

一个很重要的名词是同步加载。

  1. require("module");
  2. require("../file.js");
  3. exports.doStuff = function() {};
  4. module.exports = someValue;

熟悉nodeJS的开发者应该对这一套规范的使用非常熟悉。但是一切的前提都是在服务器端使用,那么就意味着并不适合在浏览器端来使用,同时“同步加载”对于浏览器而言并不合适。下面来简单说说其优缺点:

优点:

缺点:
- 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
- 不能非阻塞的并行加载多个模块

AMD规范

AMD全称Asynchronous Module Definition,即异步模块定义。它的主要思想是异步加载模块,那么它显然更加适合在浏览器端使用。

AMD规范其实只有一个主要接口 define(id?, dependencies?, factory),它要在声明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到 factory 中,对于依赖的模块提前执行,依赖前置。

  1. define("module", ["dep1", "dep2"], function(d1, d2) {
  2. return someExportedValue;
  3. });
  4. require(["module", "../file"], function(module, file) { /* ... */ });

优点:

缺点:

ES6模块化

在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种(上面我们已经提到)。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。而CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。

为什么运行时确定依赖关系存在缺点呢?主要是不能够同时满足按需加载请求合并依赖管理这三个需求。

我们来看看ES6模块构建的基本过程吧:
大致来说,当 JS 引擎运行一个模块的时候,它的行为大致可归纳为以下四步:

1.解析:引擎实现会阅读模块的源码,并且检查是否有语法错误。
2.加载:引擎实现会(递归地)加载所有被引入的模块。
3.链接:引擎实现会为每个新加载的模块创建一个作用域,并且将模块中的声明绑定填入其中,包括从其他模块中引入的。
4.执行:终于,JS 引擎开始执行刚加载进来的模块中的代码。

好了,ES6模块化的思想我们已经阐述完了,那么具体的实现是怎样的呢?我们的webpack就是一个很好地实现了ES6模块化思想的工具啦。当然,webpack所做的比ES6模块化所涉及的要多得多。

webpack

在 Webpack 当中,最酷的一点就是将 Web 开发中常用的如 JavaScript、CSS 以及图片、字体等各种静态文件统称为模块,并对它们进行统一的模块化加载,预处理以及打包发布,从而让开发过程变得更为高效。而且,任何静态资源都可以视作模块,然后模块之间还可以相互依赖,通过 Webpack 对模块进行处理后,就可以打包成我们想要的静态资源。

就像前面所提到的那样,Webpack 具有和 RequireJS等模块化工具相类似的功能,但还有更多独有的新特性:

  1. 对 CommonJS 、 AMD 、ES6的语法做了兼容,以及特殊模块的 Shim 处理,也就是说基本可以无痛迁移旧项目。
  2. 对 JS、CSS、图片等资源文件都支持打包,配合 loader 加载器,也可以支持 Sass,Less 等 CSS 预处理器。
  3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如通过 babel-loader 就可以直接使用 ES6 的模块机制(当然 Webpack 2 将会直接支持 ES6 模块)。
  4. 有独立的配置文件 webpack.config.js,并可以根据环境的不同加载特定的配置文件,配置好就可以一劳永逸了。
  5. 可以将代码切割成不同的 chunk,实现按需加载,有效利用浏览器的缓存功能提升性能,从而降低了初始化时间,提高用户体验。
  6. 支持 SourceUrls 和 SourceMaps,即使打包在一起依旧方便调试。
  7. 具有强大的 Plugin 接口,大多是内部插件,使用起来比较灵活。
  8. Webpack 使用异步 IO 并具有多级缓存。这使得 Webpack 很快且在增量编译上更加快。

参考文档:
1.https://blog.jimmylv.info/2016-03-10-getting-webpack-done-and-js-module-history/
2.https://segmentfault.com/a/1190000003410285
3.http://webpack.github.io/docs/motivation.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注