现代JS模块化介绍
上一篇文章我们介绍了JS基本的作用域分隔的办法,它们有一个问题,就是命名空间还是在全局作用域里。这篇文章我们来说说Modern Javascript开发中常用到的 CommonJS,AMD,UMD,Native JS 等模块化规范,这些规范都是成熟的规范,可以解决之前的问题。
WTF?不就是搞个Class出来么,JS你为啥搞这么多规范,至于么,而且每个规范都对应一个厚厚的文档。
没事,看完本文你就秒懂了。
CommonJS
CommonJS是一个JS自愿者组织搞出来的标准。比较适合在服务器端运行的场景,所以在node.js 里用得最多。
它的概念很简单,就是把不同的模块(class)写在不同的文件里,每个文件就相当于一个闭包作用域,最后在主程序里引用这个文件使用。这太容易理解了,PHP不就是这样的么。
//name.js
var first_name = 'Tom';
var last_name = ' Cruise';
var fullname = function (){
return first_name + last_name;
}
module.exports.first_name = first_name;
module.exports.last_name = last_name;
module.exports.fullname = fullname;
//app.js
var myName = require('./name.js');
//注意看这里,怎么调用模块的属性和方法
console.log(myName.first_name); // Tom
console.log(myName.last_name); // Cruise
console.log(myName.fullname); // Tom Cruise
这个定义调用的过程倒是很清楚,但是你去写就会发现行不通,module.exports,require这个是从哪里来的?
你需要使用这种模块化加载方式,得先加载一个库 requirejs.org;
如果是在 node.js环境下,可以直接使用。
还有就是,这里有2个文件,实际使用可能会有n个文件,这在服务器端使用没问题的(就像PHP),但我怎么在
浏览器端使用? CommonJS对模块文件的加载,是同步的,也就是说,浏览器得把所有模块文件加载完,程序才能运行。一个一个加载?太不靠谱了吧。
在本次教程的后期,我们会介绍一个概念,打包(bundle), 就是如果有多个文件,打包工具会把它们合并成一个文件,再在浏览器端使用。
学个小JS真费劲啊,麻烦死了。
上面的那个模块文件,我们稍微改一下,让它更容易编写://name.js
function myName(){
this.first_name = 'Tom';
this.last_name = ' Cruise';
this.fullname = function (){
return this.first_name + this.last_name;
}
}
module.exports = myName;
意思是先做成一个对象,然后整体导出,这样写得简单点,在外面使用是一样的。
AMD
CommonJS加载模块是同步的,AMD就是对CommonJS加载方式修改了一下,使它支持异步加载,主要是在浏览器端使用,所以AMD的全称是”Asynchronous Module Definition”,意思就是”异步模块定义”。
异步加载的好处就是,等模块下载好了我再执行逻辑,没下载好就不执行。
我们看看怎么写。首先,AMD模块也是在require.js的环境下运行的。
由于在浏览器端使用,所以所有代码写一个文件里:
define(['myName'], function() {
return {
first_name:'Tom',
last_name:' Cruise',
fullname: function (){
return this.first_name + this.last_name;
}
}
});
我们看到,AMD在定义的模块的时候,写得还是很清晰的,define关键字,然后在数组里起一个模块的名字,最后在闭包里返回一个对象就可以了。
然后我们看看在主程序中如何调用,这里是关键,调用方式的不同,让AMD具备了异步能力:
//使用
require(['myName'], function (myName) {
console.log(myName.fullname());
});
和CommonJS相比,同样是用require方法,但是:
- 模块是从数组里拿,而不是从文件;
- 多了一个回调参数,回调里面写具体的业务逻辑;
写在回调里面的逻辑只有当模块myName加载完毕后才会执行,所以它可以实现不同模块异步加载,异步执行。
另外,CommonJS 只能输出对象,而AMD方式支持对象,函数,构造器,字符串,json等数据形式作为模块输出。
但是AMD不支持io和文件形式的模块,那是服务器端才有的特性。
UMD
UMD(Universal Module Definition)的发明是为了让代码即能在浏览器端使用,也能在服务器端使用,它其实就是把CommonJS和AMD标准合并了。
我们来看下代码体会一下:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['myModule'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('myModule'));
} else {
// 全局变量( root就是window对象)
root.returnExports = factory(root.myModule);
}
}(this, function (myModule) {
// 模块内部定义
var first_name = 'Tom';
var last_name = ' Cruise';
var fullname = function (){
return first_name + last_name;
}
// 暴露在外面的部分
return {
first_name: first_name,
fullname: fullname
}
}));
我们看到整个逻辑无非是做了一个判断,在不同的环境执行不同的标准,但模块输出部分是差不多的。
CMD
国内有一个叫做玉伯的大神,搞了一个和require.js差不多的sea.js库,并推出了一个和AMD差不多的标准,叫CMD。这个项目现在已经关闭了,我就不做过多介绍了。
Native JS
你如果可以看到这里还没放弃,我真的很佩服你。
这篇文章到这里才是关键,前面介绍的内容或多或少都有点过时了,到了现在,js发生了一些重大的变化趋势:
- JavaScript新的模块标准导致了SeaJS和RequireJS的过时
- 原生选择器的良好支持,导致人们对jQuery不再那么依赖
- Array和Object上面一些新特性的出现,导致underscore和lodash的作用减弱
所以,现在学习ES6及更高级的标准,还有Node.js才是王道。
ES6 就对之前介绍的复杂模块化方法进行了整合和优化,具备诸多优点,它是原生的。
我们快速的看一下ES6模块化的写法:
// lib/counter.js
var counter = 1;
function increment() {
counter++;
}
function decrement() {
counter--;
}
module.exports = {
counter: counter,
increment: increment,
decrement: decrement
};
// src/main.js
var counter = require('../../lib/counter');
counter.increment();
console.log(counter.counter); // 1
仔细观察它输出的方式,counter,increment,decrement都是独立的,互不影响,所以counter.increment(),它不会给属性counter加1。
这种写法保证了足够的灵活性。
当然,如果你想让counter,increment,decrement这几个元素组合成一个类,像一个Class运作,可以这样写:
// lib/counter.js
export let counter = 1;
export function increment() {
counter++;
}
export function decrement() {
counter--;
}
// src/main.js
import * as counter from '../../counter';
console.log(counter.counter); // 1
counter.increment();
console.log(counter.counter); // 2
这样counter.increment()就会和counter.counter联动了。
是不是超级强大。
更深入的内容,请看ES6文档。
最后
模块化的事情基本讲完了,但是蛋疼的事情并未结束。真正愉快的玩耍JS,你还需要了解打包(bundle),我会在下一次文章中介绍如下内容:
- 为什么需要打包
- 打包有哪些方法
- ECMAScript的模块加载 API
- 以及更多
敬请关注。
最后吐槽一下,js是一个蛋疼的语言,PHP是世界上最好的语言。