前端模块化的前世此生

凡是开发大型应用程序,模块块必然是不可或缺的一部分。那么什么是模块化呢?其实模块化就是将一个复杂的系统分解成多个独立的模块的代码组织方式。在很长的一段时间里,前端只能经过一系列的<script>标签来维护咱们的代码关系,可是一旦咱们的项目复杂度提升的时候,这种简陋的代码组织方式即是如噩梦般使得咱们的代码变得混乱不堪。因此,在开发大型Javascript应用程序的时候,就必须引入模块化机制。因为早期官方并无提供统一的模块化解决方案,因此在群雄争霸的年代,各类前端模块化方案层出不穷。本文将从最先期的IFEE闭包方案到如今的ES6 Modules, 追根溯源,带你详细了解前端模块化的前世此生。javascript

IIFE

模块化的一大做用就是用来隔离做用域,避免变量冲突。而Javascript没有语言层面的命名空间概念,只能将代码暴露到全局做用域下。在刀耕火种的年代,做为脚本语言的Javascript为了不全局变量污染,只能使用闭包来实现模块化。好在咱们能够利用自执行函数(IIFE)来执行代码,从而避免变量名泄漏到全局做用域中:css

(function(window) { window.jQuery = { // 这里是代码 }; })(window); 

虽然IIFE能够有效解决命名冲突的问题,可是对于依赖管理,仍是一筹莫展。因为浏览器是从上至下执行脚本,所以为了维持脚本间的依赖关系,就必须手动维护好script标签的相对顺序。html

AMD

AMD (Asynchronous Module Definition)也是一种 JavaScript模块化规范。从名字上能够看出,它主要提供了异步加载的功能。对于多个JS模块之间的依赖问题,若是使用原生的方式加载代码,随着加载文件的增多,浏览器会长时间地失去响应,而AMD可以保证被依赖的模块尽早地加载到浏览器中,从而提升页面响应速度。因为该规范原生Javascript没法支持,因此必须使用相应的库来实现对应的模块化。RequireJS就是实现了该规范的类库,实际上AMD也是其在推广过程当中的产物。前端

利用RequireJS来编写模块,全部的依赖项必须提早声明好。在导入模块的时候,也会先加载对应的依赖模块,而后再执行接下来的代码,同时AMD模块能够并行加载全部依赖模块,从而很好地提升页面加载性能:java

define('./index.js',function(code){ // code 就是index.js 返回的内容 return { sayHello: function(name) { return "Hello, " + name; } } }); 

CMD

CMD(Common Module Definition)最初是由阿里的玉伯提出的,同AMD相似,使用CMD模块也须要使用对应的库SeaJS。SeaJS所要解决的问题和requireJS同样,可是在使用方式和加载时机上有所不一样:node

define(function(require) { //经过require引用模块 var path=require.resolve('./cmdDefine'); alert(path); }); 

CMD加载完某个依赖模块后并不执行,只是下载而已,在全部依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是彻底一致的。若是使用require.async()方法,能够实现模块的懒加载。jquery

CommonJS

随着Javasript应用进军服务器端,业界急需一种标准的模块化解决方案,因而,CommonJS(www.commonjs.org)应运而生。它最初是由Kevin Dangoor在他的这篇博文中首次提出。webpack

这是一种被普遍使用的Javascript模块化规范,你们最熟悉的Node.js应用中就是采用这个规范。在Node.js中,内置了module对象用来定义模块, require函数用来加载模块文件,代码以下:git

// utils.js 模块定义 var add = function(a, b) { return a + b; }; module.exports = { add: add }; // 加载模块 var utils = require('./utils'); console.log(utils.add(1, 2)); 

此种模块化方案特色就是:同步阻塞式加载,没法实现按需异步加载。另外,若是想要在浏览器中使用CommonJS模块就须要使用Browserify进行解析:es6

npm install browserify -g
browserify utils.js > bundle.js

固然,你也可使用gulp, webpack等工具进行解析打包后引入到浏览器页面中去。

UMD

上面介绍的CommonJS和AMD等模块化方案都是针对特定的平台,若是想要实现跨平台的模块化,就得引入UMD的模块化方式。UMD是通用模块定义(Universal Module Definition)的缩写,使用该中模块化方案,能够很好地兼容AMD, CommonJS等模块化语法。

接下来,让咱们经过一个简单地例子看一下如何使用和定义UMD模块:

(function(root, factory) { if(typeof define === 'function' && define.amd) { define(['jquery'], factory); } else if(typeof module === 'object' && typeof module.exports === 'object') { var jquery = require('jquery'); module.exports = factory(jquery); } else { root.UmdModule = factory(root.jQuery); } }(this, function(jquery) { // 如今你能够利用jquery作你想作的事了 })); 

这种模块定义方法,能够看作是IIFE的变体。不一样的是它倒置了代码的运行顺序,须要你将所需运行的函数做为第二个参数传入。因为这种通用模块的适用性强,不少JS框架和类库都会打包成这种形式的代码。

ES6 Modules

对于ES6来讲,没必要再使用闭包和封装函数等方式进行模块化支持了。在ES6中,从语法层面就提供了模块化的功能。然而受限于浏览器的实现程度,若是想要在浏览器中运行,仍是须要经过Babel等转译工具进行编译。ES6提供了importexport命令,分别对应模块的导入和导出功能。具体实例以下:

// demo-export.js 模块定义 var name = "scq000" var sayHello = (name) => { console.log("Hi," + name); } export {name, sayHello}; // demo-import.js 使用模块 import {sayHello} from "./demo-export"; sayHello("scq000"); 

对于具体的语法细节,想必你们在平常使用过程当中都已经轻车熟路了。但对于ES6模块化来讲,有如下几点特性是须要记住的:

  • ES6使用的是基于文件的模块。因此必须一个文件一个模块,不能将多个模块合并到单个文件中去。
  • ES6模块API是静态的,一旦导入模块后,没法再在程序运行过程当中增添方法。
  • ES6模块采用引用绑定(能够理解为指针)。这点和CommonJS中的值绑定不一样,若是你的模块在运行过程当中修改了导出的变量值,就会反映到使用模块的代码中去。因此,不推荐在模块中修改导出值,导出的变量应该是静态的。
  • ES6模块采用的是单例模式,每次对同一个模块的导入其实都指向同一个实例。

Webpack中的模块化方案

做为现代化的前端构建工具,Webpack还提供了丰富的功能可以使咱们更加轻易地实现模块化。利用Webpack,你不只能够将Javascript文件进行模块化,同时还能针对图片,css等静态资源进行模块化。你能够在代码里使用CommonJS, ES6等模块化语法,打包的时候你也能够根据需求选择打包类型,如UMD, AMD等:

module.exports = {
  //...
  output: {
    library: 'librayName',
    libraryTarget: 'umd', // 配置输出格式
    filename: 'bundle.js'
  }
};

另外,ES6模块好处不少,可是并不支持按需加载的功能, 而按需加载又是Web性能优化中重要的一个环节。好在咱们能够借助Webpack来弥补这一缺陷。Webpack v1版本提供了require.ensureAPI, 而2.x以后使用了import()函数来实现异步加载。具体的代码示例能够查看我以前所写的前端性能优化之加载技术 这篇文章。

总结

模块化方案解决了代码之间错综复杂的依赖关系,不只下降了开发难度同时也让开发者将精力更多地集中在业务开发中。随着ES6标准的推出,模块化直接成为了Javascript语言规范中的一部分。这也意味着CommonJS, AMD, CMD等模块化方案终将退出历史的舞台。固然,要实现彻底ES6模块化的使用,还须要一段长时间的等待。那么,在这段过渡的时间里,咱们可能仍然须要维护旧有的代码,使用传统的模块化方案来构建应用。对于前端工程师来讲,系统地了解主流的模块化方案就显得十分必要了。最后,让咱们再一次回顾一下各类模块化方式的特色:

模块化方案 加载 同步/异步 浏览器 服务端 模块定义 模块引入
IFEE 取决于代码 取决于代码 支持 支持 IFEE 命名空间
AMD 提早预加载 异步 支持 构建工具r.js define require
CMD 按需加载 延迟执行 支持 构建工具spm define define
Common 值拷贝,运行时加载 同步 原生不支持,须要使用browserify提早打包编译 原生支持 module.exports require
UMD 取决于代码 取决于代码 支持 支持 IFEE 命名空间
ES Modules(ES6) 实时绑定,动态绑定,编译时输出 同步 需用babel转译 需用babel转译 export import

参考资料

http://javascript.ruanyifeng.com/nodejs/module.html

《你不知道的Javascript》

https://www.infoq.cn/article/es6-in-depth-modules?utm_source=articles_about_ES6-In-Depth&utm_medium=link&utm_campaign=ES6-In-Depth

——本文首发于我的公众号,转载请注明出处———

 
微信扫描二维码,关注个人公众号

 

最后,欢迎你们关注个人公众号,一块儿学习交流。

相关文章
相关标签/搜索