导语:本文带你去了解一下JavaScript模块化的前世今生,包括但不限于JavaScript模块化、模块化规范、模块加载器和模块打包工具等。本文不是一个深度剖析JavaScript模块相关话题的文章,仅是一个能够让你10分钟快速了解JavaScript模块化相关知识的介绍。
作者:徐江伟--腾讯前端工程师
@IMWeb前端社区
现在JavaScript技术的发展可能会让你应接不暇。身为一线搬砖工的你对雨后春笋般的前端工具和框架越来越疲于学习和暇接。有时候,你可能不自主的问,webpack是什么玩意?browserify又是什么东东?AMD和CommonJS这都是啥,又有啥关系和区别?
我们首先看一下模块化的定义是什么,以下定义来自百度百科:
模块化: 模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。
乍看起来有点高深,简单地讲就是模块化可以让我们将代码分功能和业务等维度来切分成小的组织单元,然后根据需要来更好的组织代码,增加代码的可维护性、复用性等。其实,确切的讲,我们写代码完全可以不用模块化。但正如提到的,模块化可以显著地增加代码的复用性、可维护性和扩展性等优点,是自上世纪开始写代码后人们在实践总结出来的优秀实践原则。
具体到JavaScript中来讲,模块化带来了以下几点优点:
遗憾的讲,在ES5版本和之前的版本中,JavaScript并没有模块化的概念。行业优秀的工程师们通过各种各样的方式来模拟JavaScript的模块化。来,下面我们就看看常用的两种模拟方法是怎么样的。
立即执行函数,英文为Immediately Invoked Function Expression,简称为IIFE。通常结构如下:
(function(){ // ...})()
从中可以看出,IIFE是一个一旦被声明就会被立即执行的匿名函数。那么匿名的立即执行函数带来了哪些特点呢:
因此,我们可以说,IIFE为一个小的模块。但是,从中我们可以看出,它并没有提供良好的依赖管理的实现。
模块化的模式是在立即执行函数上更进了一步,我们将需要提供给第三方的变量return出来,如下:
// 将封装的模块赋值给一个变量
var helloApi = function(){
// 内部实现
function sayHello(){
console.log('Hello');
}
// 对外暴露API
return { sayHello: sayHello
}
}()
然后,我们可以按照如下方式来调用模块的API了:
helloApi.sayHello(); // Hello
以上,实现了简单的代码模块化。随着JavaScript模块化实践的不断推进,企业和社区冒出了很多不同的模块化定义和使用的库,如seaJS、moduleJS、requireJS等,各自语法大同小异,也各有优缺点。
模块化的规范定义了我们如何来写模块化的代码。其实,在ES6发布之前,JavaScript语言并没有推出官方的规范来确定模块化定义语法。上面我们提到了,JavaScript模块化实践的不断推进,企业和社区冒出了很多不同的模块化定义和使用的库,因此也有不同的规范版本。包括ES6等一些比较优秀的规范包括:
我们挨个看看,每一个是如何来定义和实现模块化的。
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出的。看一下AMD的语法:
define(['./a', './b'], function(a, b) {
// 依赖必须一开始就写好
a.doSomething();
// 此处略去 100 行
b.doSomething();
...
})
CMD是seaJS在推广过程中对模块化定义的规范产出的。与上面的例子类似,我们看看CMD的语法:
define(function(require, exports, module) {
var a = require('./a') ;
a.doSomething() ;
var b = require('./b');
// 依赖可以就近书写
b.doSomething();
})
CommonJS是在Nodejs中定义模块中使用非常广泛的规范,它使用require和module.exports定义模块的依赖和API导出,简单的例子如下:
var dep1 = require('./dep1');
var dep2 = require('./dep2');
module.exports = function(){
// ...
}
UMD基本上是统一了浏览器端和后端JS(Nodejs)的模块化定义,看下它的定义便可窥知:
5、ES6 module
ES6的发布基本上第一次在JavaScript语言层面增加了对模块化的支持,它使用了import和export来定义依赖和模块导出。看一个简单的例子:
// hello.js
// 导出sayHello方法
export function sayHello(){
console.log('Hello');
}
// other.js
// 引入hello模块的sayHello方法
import { sayHello } from './hello';
sayHello(); // => Hello
说白了,模块加载器来解释和加载模块化的代码。它的职责包括:
国内用的比较多的包括requireJS、moduleJS、seaJS等。特别需要说明的一点是,模块加载器是执行在浏览器端,需要加载到浏览器里,在webapp运行阶段执行。
模块化打包器或者说模块化打包工具的出现,基本上说可以替代了模块加载器,可以完全将模块加载器的事都给干了。但是,有一点与模块加载器不同的是,它运行在webapp构建阶段。它的特点如下:
所有的文件都在构建的时候打包为一个文件,浏览器端只加载这一个文件完全够了。比较火的模块打包工具包括:webpack和browserify等。
到这里,相信你对JavaScript的模块化的前世今生和关键的名词都比较了解了。这里给出各个概念的解释:
腾讯IMWeb前端社区
免费大咖直播课
定期优质干货文章推送
微信ID:IMWebTech