从小工到专家 点击上方蓝字 关注我们☝
题图:by yentl jacobs from Pexels
前段时间,公众号后台收到几个小伙伴留言说写点 JavaScript 模块化方面的东西。今天就来一起看看 JavaScript 的模块。
什么是模块化
模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。 ——百度百科
说的简单点就是模块就是实现特定功能的一组方法,模块化将使代码更好的管理、维护和使用
从历史说起
最早,我们这样写代码,
function foo(){
//...
}
function bar(){
//...
}
但是,这污染了全局变量,很容易命名冲突。
简单封装:Namespace 模式
命名空间模式解决了上面的两个问题:一是全局变量污染的问题,二是可能的名字冲突问题。虽然JavaScript 没有特别支持命名空间, 但命名空间模式在JavaScript中并不难实现,可以把模块写成一个对象,所有的模块成员都放到这个对象里面
var MYAPP = {
foo: function(){},
bar: function(){}
}
MYAPP.foo();
上面的函数foo()和bar(),都封装在MYAPP对象里。使用的时候直接调用这个对象的属性即可 MYAPP.foo()。
这虽然减少了全局变量的数量,但是它本质上是对象一点不安全,外部很容易改变内部的状态
IIFE模式:立即执行函数写法
因为函数的局部作用域,使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
var Module = (function(){
var _private = "safe now";
var foo = function(){
console.log(_private)
}
return {
foo: foo
}
})()
Module.foo();
Module._private; // undefined
这样function内部的变量就对全局隐藏了,达到是封装的目的。但是这样还是有缺陷的,Module这个变量还是暴漏到全局了,随着模块的增多,全局变量还是会越来越多。
使用了IIFE模式后好像已经有点模块化那意思了,但是如果模块很多,我们一个模块需要另一个模块就需要改改了
var Module = (function($){
var _$body = $("body"); // we can use jQuery now!
var foo = function(){
console.log(_$body); // 特权方法
}
// Revelation Pattern
return {
foo: foo
}
})(jQuery)
Module.foo();
上述做法就是我们模块化的基础,目前,通行的JavaScript模块规范主要有两种:CommonJS 和 AMD。当然还有 玉伯的CMD 以及正在逐步普及的ES6 module,还有统一的通用规范UMD(本文不会讲到)
各种模块化规范
在ES6之前 JavaScript 并没有内置的标准模块系统,于是社区的大牛们就制定了一些模块加载方案。
CommonJS
CommonJS 是nodejs也就是服务器端广泛使用的模块化机制。 该规范的主要内容是:模块必须通过module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。
// math.js
exports.add = function(a, b){
return a + b;
}
// main.js
var math = require('math')
console.log(math.add(1, 2)); // 3
commonJS用同步的方式加载模块,在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但这在浏览器端问题多多,限于网络原因,更合理的方案是使用异步加载。
AMD和require.js
AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范。由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎 RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出
AMD也采用 require() 语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
// 第一个参数[module],是一个数组,里面的成员就是要加载的模块;
// 第二个参数callback,则是加载成功之后的回调函数
// 等到module加载完成之后,这个回调函数才会运行
我们可以来看看前面的例子用 AMD 规范的写法
// math.js
define(function (){
var add = function (a, b){
return a + b;
};
return {
add: add
};
});
// main.js
require(['math'], function (math) {
var sum = math.add(1, 2);
});
默认情况下,require.js假定这加载的模块与main.js在同一个目录,然后自动加载。我们可以使用require.config()方法对模块的加载行为进行自定义
// main.js
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
}
});
requireJS主要解决两个问题
-
js加载的时候浏览器会停止页面渲染,实现js文件的异步,加载避免网页失去响应;
-
管理模块之间的依赖性,便于代码的编写和维护;
CMD和sea.js
CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载时机上有所不同:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
Sea.js 的初衷是为了让 CommonJS Modules/1.1 的模块能运行在浏览器端,但由于浏览器和服务器的实质差异,实际上这个梦无法完全达成,也没有必要去达成。
在 SeaJS 的世界里,一个文件就是一个模块。所有模块都遵循 CMD规范,我们可以像在 Node环境中一样来书写模块代码,我们还是来实现这个加法运算模块:
// math.js
define(function(require, exports, module) {
var $ = require('jquery');
exports.add = function(a,b){
return a+b;
}
});
// main.js
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});
ES6 Module
值得庆祝的是 ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
// math.js
export function add(a, b) {
return a + b;
};
// main.js
import math from './math';
function test() {
return math.add(2, 3);
}
ES6 模块功能主要由两个命令构成: export和 import。 export命令用于规定模块的对外接口, import命令用于输入其他模块提供的功能。
参考链接:
CommonJS:http://wiki.commonjs.org/wiki/CommonJS AMD:https://github.com/amdjs/amdjs-api/wiki/AMD requirejs:http://requirejs.org/ CMD:https://github.com/seajs/seajs/issues/242 sea.js:http://www.zhangxinxu.com/sp/seajs/ ES6 Module:http://es6.ruanyifeng.com/#docs/module
本文完
往期精彩回顾:
左手代码右手砖,抛砖引玉
给点个赞,好不好啊