RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
基本形式:
require([module], callback);
下面介绍一下require的简单应用以及原理
更多的api可以查看官方网站http://requirejs.org/docs/api.html
资源地址:https://github.com/requirejs/requirejs
模块引用
//引入require.js,daya-main表示加载完requirejs后立即加载的js,一般是主模块
<script data-main="js/app.js" src="js/require.js"></script>
//配置路径
requirejs.config({
//By default load any module IDs from js/lib
baseUrl: 'js/lib',
//except, if the module ID starts with "app",
//load it from the js/app directory. paths
//config is relative to the baseUrl, and
//never includes a ".js" extension since
//the paths config could be for a directory.
paths: {
app: '../app'
}
});
//加载资源,执行回调
// Start the main app logic.
requirejs(['jquery', 'canvas', 'app/sub'],
function ($, canvas, sub) {
//jQuery, canvas and the app/sub module are all
//loaded and can be used here now.
});
模块定义
//Definition Functions
//my/shirt.js now does setup work
//before returning its module definition.
define(function () {
//Do setup work here
return {
color: "black",
size: "unisize"
}
});
//Definition Functions with Dependencies
//my/shirt.js now has some dependencies, a cart and inventory
//module in the same directory as shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
//return an object to define the "my/shirt" module.
return {
color: "blue",
size: "large",
addToCart: function() {
inventory.decrement(this);
cart.add(this);
}
}
}
);
代码结构
- my/cart.js
- my/inventory.js
- my/shirt.js
加载非AMD模块方式
(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性。
require.config({
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
原理
这里简单解析一下requirejs工作原理
我们下个断点看看requirejs执行前后的head头
执行后
由此我们可以看出 requirejs模块加载的是依赖head.appendChild(script)
首先是函数的主入口
requirejs = function (deps, callback, errback, optional)
requirejs首先会获取依赖的deps,在一个全局变量registry中获取
function getModule(depMap) {
var id = depMap.id,
mod = getOwn(registry, id);
if (!mod) {
mod = registry[id] = new context.Module(depMap);
}
return mod;
}
没有对应的模块则会根据模块名称加载一个新的module
req.load = function (context, moduleName, url)
创建过程 首先创建一个script
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};
然后设置监听事件 load
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
讲load添加到head
head.appendChild(node);
下面是onScriptLoad监听事件和getScriptData函数
onScriptLoad: function (evt) {
//Using currentTarget instead of target for Firefox 2.0's sake. Not
//all old browsers will be supported, but this one was easy enough
//to support and still makes sense.
if (evt.type === 'load' ||
(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
//Reset interactive script so a script node is not held onto for
//to long.
interactiveScript = null;
//Pull out the name of the module and the context.
var data = getScriptData(evt);
context.completeLoad(data.id);
}
}
function getScriptData(evt) {
//Using currentTarget instead of target for Firefox 2.0's sake. Not
//all old browsers will be supported, but this one was easy enough
//to support and still makes sense.
var node = evt.currentTarget || evt.srcElement;
//Remove the listeners once here.
removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
removeListener(node, context.onScriptError, 'error');
return {
node: node,
id: node && node.getAttribute('data-requiremodule')
};
}
laod完成后获取script资源创建module并注入registry。
关于module结构
Module = function (map) {
this.events = getOwn(undefEvents, map.id) || {};
this.map = map;
this.shim = getOwn(config.shim, map.id);
this.depExports = [];
this.depMaps = [];
this.depMatched = [];
this.pluginMaps = {};
this.depCount = 0;
/* this.exports this.factory
this.depMaps = [],
this.enabled, this.fetched
*/
};
这是加载单个dependency的过程,requirejs加载dependency的时候是
require([module], callback);
那么是如何保证多个module加载完成后才去执行callback?
我们可以看到函数localRequire,返回return localRequire 连续调用
function localRequire(deps, callback, errback)
//code
//Mark all the dependencies as needing to be loaded.
context.nextTick(function () {
//Some defines could have been added since the
//require call, collect them.
intakeDefines();
requireMod = getModule(makeModuleMap(null, relMap));
//Store if map config should be applied to this require
//call for dependencies.
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
//code
return localRequire;
}
checkLoaded会检测模块有没有加载完成,用定时器去检测状态。
if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
checkLoadedTimeoutId = setTimeout(function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}, 50);
}
总结:requirejs是非常轻量级的js库,压缩之后的仅14kb;requirejs按需加载js,无需像以前一样大量引入js文件;requirejs遵循AMD规则,异步加载js;requirejs支持模块化编程,强调模块的独立性与模块之间依赖性。