一、JavaScript模块化的必要性
随着网站逐渐变成"互联网应用程序(WebApp)",嵌入网页的Javascript代码越来越庞大,越来越复杂。 网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。 Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。 但是,Javascript不是一种模块化编程语言,它不支持"类"(class),更遑论"模块"(module)了。(正在制定中的ECMAScript标准第六版,将正式支持"类"和"模块",但还需要很长时间才能投入实用。) Javascript社区做了很多努力,在现有的运行环境中,实现"模块"的效果。
二、Javascript模块化的写法-原始写法
模块就是实现特定功能的一组方法。
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
比如:tool.js
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
2.2Javascript模块化的写法-对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
2.3Javascript模块化的写法-立即执行函数写法(闭包)
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
2.4Javascript模块化的写法-放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。
2.4Javascript模块化的写法-宽放大模式
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"。
三、模块规范
先想一想,为什么模块很重要?
因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。
但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。目前,通行的Javascript模块规范共有两种:CommonJS和AMD(CMD)。我主要介绍AMD,但是要先从CommonJS讲起。
四、CommonJS(用于服务端(node))
2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。 这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。 node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。
定义模块
// module.js
let name = 'liakng xie';
let sayName = function () {
console.log(name);
};
module.exports = { name, sayName }
// 或者
exports.sayName = sayName;
加载模块
// 通过 require 引入依赖 let module = require('./module.js'); module.sayName(); // likang xie
六、 AMD(用于浏览器环境)
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
定义模块
define(['module'], function() { let name = 'likang xie'; function sayName() { console.log(name); } return { sayName } })
使用模块
// 通过 require 引入依赖 require(['module'], function(mod) { mod.sayName(); // likang xie })
七、CMD(用于浏览器环境)
跟 AMD 类似,现在基本用不到,定义模块与使用模块
define(function(require, exports, module) { // 通过 require 引入依赖 var otherModule = require('./otherModule'); // 通过 exports 对外提供接口 exports.myModule = function () {}; // 或者通过 module.exports 提供整个接口 module.exports = function () {}; })
八、ES6模块(用于浏览器环境)
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
a.js
export default { name: 'likang xie' }
b.js
// 输出多个变量 export let name = 'likang xie'; export let sayName = () => console.log(name);
使用模块
import people from 'a.js'; console.log(people); // { name: 'likang xie' }
// 将所有获取到的变量存到people里 import * as people from 'b.js'; console.log(people); // 一个module对象 { name: 'likang xie', sayName: .... } // 或者 import { name, sayName } from 'b.js';