JavaScript本身不是一种模块化言语,设想者在制造JavaScript之初应当也没有想到这么一个剧本言语的作用范畴会愈来愈大。之前一个页面的JS代码再多也不会多到哪儿去,而如今跟着愈来愈多的JavaScript库和框架的涌现,Single-page App的盛行以及Node.js的迅猛发展,假如我们还不对本身的JS代码举行一些模块化的构造的话,开辟历程会愈来愈难题,运转机能也会愈来愈低。因而,相识JS模块化编程是非常重要的。
简朴的模块
什么是模块?我以为将差别功用的函数放在一同,构成一个能完成某种或某些特定功用的团体就是一个模块,因而如许:
function add(a, b) {
return a + b;
}
function divide(a, b) {
return a / b;
}
云云简朴的两个函数就能够构成一个模块,这个模块能够举行一些数学运算。
固然没有人会这么写模块。仅仅是从“型”上来看,两个函数疏散在全局环境中,这也看不出模块的特性。模块存在于全局变量中,应当供应一个定名空间,成为模块内容的进口。那末我们能够将函数包裹在一个对象中:
var math = {
add: function(a, b) {
return a + b;
},
divide: function(a, b) {
return a / b;
}
}
如许看起来好像有模块的“型”了。然则如许还不完美,math
中的一切成员都是对外暴露的,假如个中有一些变量不愿望被修正的话那就有风险了。为了防备天下被损坏,为了保护私有变量不被修正,我们能够运用闭包。
var math = (function() {
var _flag = 0;
return {
add: function(a, b) {
return a + b;
},
divide: function(a, b) {
return a / b;
}
};
})();
外部代码只能接见返回的add
和divide
要领,内部的_flag
变量是不能接见的。关于建立对象的一些要领的诠释,能够参考我的另一篇博文,内里有较细致的诠释。
应用自实行函数的特性,我们还能够很轻易地为模块增加要领:
var math = (function(module) {
module.subtract = function(a, b) {
return a - b;
}
})(math);
模块在全局变量中的称号能够会与其他的模块发生争执,比方$
标记,虽然运用轻易,但多个模块能够都邑用它作为本身的简写,比方jQuery。我们能够在模块的构造代码顶用$
作为形参,将模块的全名变量作为参数传入,可起到防争执的结果。
var math = (function($) {
// 这里的$指的就是Math
})(math);
模块的构建头脑就是经由过程如许的体式格局逐步演变而来,下面将经由过程引见一些JS模块化编程的范例来展现怎样构造,治理和编写模块。
AMD
与 CMD
在JavaScript模块化编程的天下中,有两个范例不得不提,它们分别是AMD和CMD。如今的JS库或框架,通常模块化的,平常都是遵照了这两个范例个中之一。
AMD(Asynchronous Module Definition)
CommonJS
在说AMD之前,先要提一下CommonJS。CommonJS是为了填补JavaScript范例库过少的瑕玷而发生的,因为JS没有模块机制(ES6引入了模块体系,但浏览器周全支撑预计另有好几年),CommonJS就协助JS完成模块的功用。如今很热点的Node.js就是CommonJS范例的一个完成。
CommonJS在模块中定义要领要借助一个全局变量exports
,它用来天生当前模块的API:
/* math module */
exports.add = function(a, b) {
return a + b;
};
要加载模块就要运用CommonJS的一个全局要领require()
。加载之前完成的math
模块像如许:
var math = require('math');
加载后math
变量就是这个模块对象的一个援用,要挪用模块中的要领就像挪用一般对象的要领一样了:
var math = require('math');
math.add(1, 3);
总之,CommonJS就是一个模块加载器,能够轻易地对JavaScript代码举行模块化治理。但它也有瑕玷,它在设想之初并没有完整为浏览器环境斟酌,浏览器环境的特性是一切的资本,不斟酌当地缓存的要素,都须要从服务器端加载,加载的速率取决于收集速率,而CommonJS的模块加载历程是同步壅塞的。也就是说假如math
模块体积很大,网速又不好的时刻,悉数顺序便会住手,守候模块加载完成。
跟着浏览器端JS资本的体积愈来愈巨大,壅塞给体验带来的不良影响也愈来愈严峻,终究从,在CommonJS社区中有了差别的声响,AMD
范例诞生了。
AMD
它的特性就是异步加载,模块的加载不会影响其他代码的运转。一切依靠于某个模块的代码悉数移到模块加载语句的回调函数中去。AMD的require()
语句吸收两个参数:
// require([module], callback)
require(['math'], function(math) {
math.add(1, 3);
});
在回调函数中,能够经由过程math
变量援用模块。
AMD范例也划定了模块的定义划定规矩,运用define()
函数。
define(id?, dependencies?, factory);
它吸收三个参数:
id
这是一个可选参数,相当于模块的名字,加载器可经由过程id名加载对应的模块。假如没有供应id,加载器会将模块文件名作为默许id。
dependencies
可选,吸收一个数组参数,传入当前对象依靠的对象id。
factory
回调函数,在依靠模块加载完成后会挪用,它的参数是一切依靠模块的援用。回调函数的返回值就是当前对象的导出值。
用AMD范例完成一个简朴的模块能够如许:
define('foo', ['math'], function(math) {
return {
increase: function(x) {
return math.add(x, 1);
}
};
});
假如省去id和dependencies参数的话,就是一个完整的匿名模块。factory的参数将为默许值require
,exports
和module
加载器将完整经由过程文件途径的体式格局加载模块,同时假如有依靠模块的话可经由过程require
要领加载。
define(function(require, exports, module) {
var math = require('math');
exports.increase = function(x) {
return math(x, 1);
};
});
AMD范例也许可对加载举行一些设置,设置选项不是必需的,但天真变动设置,会给开辟带来一些轻易。
baseUrl 以字符串情势划定根目次的途径,今后在加载模块时都邑以该途径为范例。在浏览器中,事情目次的途径就是运转剧本的网页地点的途径。
{
baseUrl: './foo/bar'
}
path 能够指定需加载模块的途径,模块名与途径以键-值对的体式格局写在对象中。假如一个模块有多个可选地点,能够将这些地点写在一个数组中。
{
path: {
'foo': './bar'
}
}
关于模块途径的设置项另有packages,map。
shim
关于某些没有根据AMD范例编写的模块,比方jQuery,来讲,要使它们能被加载器加载,须要用shim
要领为其设置一些属性。在main
模块中,用require.config()
要领:
require.config({
shim: {
'jquery': {
exports: '$'
},
'foo': {
deps: [
'bar',
'jquery'
],
exports: 'foo'
}
}
});
以后再用加载器加载就能够了。
现在完成了AMD范例的库有许多,比较著名的是Require.js。
CMD(Common Module Definition)
CMD在许多处所和AMD有类似之处,在这里我只说二者的差别点。
起首,CMD范例和CommonJS范例是兼容的,比拟AMD,它简朴许多。遵照CMD范例的模块,能够在Node.js中运转。
define
与AMD范例差别的是CMD范例中不运用id
和deps
参数,只保存factory
。个中:
1.factory
吸收对象/字符串时,表明模块的接口就是对象/字符串。
define({ 'foo': 'bar' });
define('My name is classicemi.');
define.cmd
其值为一个空对象,用于推断页面中是不是有CMD模块加载器。
if (typeof define === 'function' && define.cmd) {
// 运用CMD模块加载器编写代码
}
require
此函数一样用于猎取模块接口。如需异步加载模块,运用require.async
要领。
define(function(require, exports, module) {
require.async('math', function(math) {
math.add(1, 2);
});
});
我们能够发明,require(id)
的写法和CommonJS一样是以同步体式格局加载模块。要像AMD范例一样异步加载模块则运用define.async
要领。
exports
此要领用于模块对外供应接口。
define(function(require, exports, module) {
// 对外供应foo属性
exports.foo = 'bar';
// 对外供应add要领
exports.add = function(a, b) {
return a + b;
}
});
供应接口的另一个要领是直接return包括接口键值对的对象:
define(function(require, exports, module) {
return {
foo: 'bar',
add: function(a, b) {
return a + b;
}
}
});
然则注重,不能用exports输出接口对象:
define(function(require, exports, module) {
exports = {
foo: 'bar',
add: function(a, b) {
return a + b;
}
}
});
如许写是毛病的!
替换体式格局是如许写:
define(function(require, exports, module) {
module.exports = {
foo: 'bar',
add: function(a, b) {
return a + b;
}
}
});
之前毛病的原因是在factory
内部,exports
实际上是module.exports
的一个援用,直接给exports
赋值是不会转变module.exports
的值的。
在module对象上,除了有上面提到的exports
之外,另有一些别的属性和要领。
module.id
模块的标识。
define('math', [], function(require, exports, module) {
// module.id 的值为 math
});
module.uri
模块的绝对途径,由模块体系剖析获得。
define(function(require, exports, module) {
console.log(module.uri); // http://xxx.com/path/
});
module.dependencies
值为一个数组,返回本模块的依靠。
Require.js 和 Sea.js
之前在说AMD范例的时刻提到了Require.js。它是AMD范例的代表性产物。另一个Sea.js在前端界也是大名鼎鼎了,CMD范例实际上就是它的产出。它们之间的区分也很能表现AMD和CMD范例之间的区分。
AMD的依靠须要前置誊写
define(['foo', 'bar'], function(foo, bar) {
foo.add(1, 2);
bar.subtract(3, 4);
});
CMD的依靠就近誊写即可,不须要提早声明:
同步式:
define(function(require, exports, module) {
var foo = require('foo');
foo.add(1, 2);
...
var bar = require('bar');
bar.subtract(3, 4);
});
异步式:
define(function(require, exports, module) {
...
require.async('math', function(math) {
math.add(1, 2);
});
...
});
虽然AMD也能够用和CMD类似的要领,但不是官方引荐的。
之前在引见CMD的API时,我们能够发明其API职责专注,比方同步加载和异步加载的API都分为require
和require.async
,而AMD的API比较多功用。
总而言之,援用玉伯的总结:
1. Require.js同时适用于浏览器端和服务器环境的模块加载。Sea.js则专注于浏览器端的模块加载完成。经由过程Node扩大也能够运转于Node环境中。
2. Require.js -> AMD,Sea.js -> CMD。
3. RequireJS 在尝试让第三方类库修正本身来支撑 RequireJS,现在只要少数社区采纳。Sea.js 不强推,采纳自立封装的体式格局来“海纳百川”,现在已有较成熟的封装战略。
4. Sea.js的调试东西比较完整,Require.js调试比较不轻易。
5. RequireJS 采用的是在源码中预留接口的情势,插件范例比较单一。Sea.js 采用的是通用事宜机制,插件范例更雄厚。
怎么看都像是在自诩啊= =,固然它有这个资历