最初我们是这么写代码的
function foo(){
//...
}
function bar(){
//...
}
Global 被污染,很容易命名冲突
var MYAPP = {
foo: function(){},
bar: function(){}
}
MYAPP.foo();
这样减少 Global 上的变量数目,但本质是对象
var Module = (function(){
var _private = "safe now";
var foo = function(){
console.log(_private)
}
return {
foo: foo
}
})()
Module.foo();
Module._private; // undefined
再接下来,我们可以在模块中引入一些其他依赖,比如传入一个jquery的引用
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();
这就是模块模式,也是现代模块实现的基石。
script(src="jquery.js")
script(src="app.js")
一开始我们用script标签加载js是这样的,这样的写的缺点是顺序很重要,最基础的依赖只能放最前面,因为浏览器按照< script>在网页中出现的顺序,读取Javascript文件,然后立即运行。
同时,浏览器采用”同步模式”加载< script>标签,也就是说,页面会”堵塞”(blocking),等待javascript文件加载完成。如果我们通过异步的方式加载js,又会导致在onload的回调中,不能调到js中的方法。
这就带来了几个问题:
LABjs帮助我们更有效地管理Javascript加载。 LAB(Loading and Blocking),Loading 指异步并行加载,Blocking 是指同步等待执行。LAB通过优雅的语法(script 和 wait)实现了这两大特性,核心价值是性能优化。LABjs 是一个文件加载器。
YUI实现了基于模块的依赖管理
// YUI - 编写模块
YUI.add('dom', function(Y) {
Y.DOM = { ... }
})
// YUI - 使用模块
YUI().use('dom', function(Y) {
Y.DOM.doSomeThing();
// use some methods DOM attach to Y
})
YUI的原理如下
// Sandbox Implementation
function Sandbox() {
// initialize the required modules
for (i = 0; i < modules.length; i += 1) {
Sandbox.modules[modules[i]](this);
}
}
所有依赖模块通过 attach 的方式被注入沙盒(attach:在当前 YUI 实例上执行模块的初始化代码,使得模块在当前实例上可用)。也就是说,在使用add 方法之后,YUI对模块进行了初始化。
script(src="http://yui.yahooapis.com/3.0.0/build/yui/yui-min.js")
script(src="http://yui.yahooapis.com/3.0.0/build/dom/dom-min.js")
我们要解决的问题之一是script标签的请求太多,那么可以通过这样的方式:
script(src="http://yui.yahooapis.com/combo?
3.0.0/build/yui/yui-min.js&
3.0.0/build/dom/dom-min.js")
这样的方式需要web服务器的支持。
commonJs标准解决的不止是浏览器,在node.js中得到了支持
// math.js
exports.add = function(a, b){
return a + b;
}
使用的时候
// main.js
var math = require('math') // ./math in node
console.log(math.add(1, 2)); // 3
值得一提的是,同步加载对服务器/本地环境根本不是问题。
CMD(Common Module Definition)是SeaJS 对模块定义的规范化产出,而AMD(Async Module Definition)是RequireJS 对模块定义的规范化产出。
如果require是异步的,那么以下代码就会报错:
//CommonJS Syntax
var Employee = require("types/Employee");
function Programmer (){
//do something
}
Programmer.prototype = new Employee();
//如果 require call 是异步的,那么肯定 error
//因为在执行这句前 Employee 模块根本来不及加载进来
所以在AMD中就通过wrapper
//AMD Wrapper
define(
["types/Employee"], //依赖
function(Employee){ //这个回调会在所有依赖都被加载后才执行
function Programmer(){
//do something
};
Programmer.prototype = new Employee();
return Programmer; //return Constructor
}
)
// 在helper.js中
define('helper', ['jquery'], function($){
return {
trim: function(str) {
return $.trim(str);
}
}
})
第一个参数为模块名,如果不写的话,就是以文件路径为模块名(相应的,在require调用中的依赖就为路径字符串)。第二个参数为模块依赖,是一个数组,可以配置多个依赖。最后在模块依赖加载之后,会触发第三个参数的回调,参数的顺序为依赖的模块顺序。在回调中返回一个模块结果。接下来看一下加载模块
// 在所需js文件中
require(['helper'], function(helper){
var str = helper.trim(' sysuzhyupeng ');
console.log(str);
});
加载模块中第一个参数同样是依赖的数组,使用方式和定义模块基本一致。
require.js以一个相对于baseUrl的地址来加载所有代码,比如上面我们require的时候,会在helper前面加上baseUrl路径。baseUrl可以在require.js加载的script标签以data-main属性配置,也可以自己配置
requirejs.config({
baseUrl: '',
paths: {
'jquery': 'lib/jquery'
}
})
那么对于非baseUrl下的模块,可以通过配置paths字段来指定路径。
require.js以< script>标签来加载文件,可以跨域获取cdn上的文件。当我们调用require的时候,它加载后马上执行。
不支持AMD的模块只需要在配置文件中的shim属性中配置即可。