require.config

require.config设置require.js模板载入选项

    // 定义config
req.config = function (config) {
return req(config);
};
// 载入config配置项
req = requirejs = function (deps, callback, errback, optional) {
var context, config,
contextName = defContextName; // 假设deps是config对象
if (!isArray(deps) && typeof deps !== 'string') {
config = deps;
if (isArray(callback)) {
deps = callback;
callback = errback;
errback = optional;
} else {
deps = [];
}
} if (config && config.context) {
contextName = config.context;
}
// 获取默认的context
context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
}
// 配置模块载入选项
if (config) {
context.configure(config);
}
// 当deps为config时,此调用木有实质作用
return context.require(deps, callback, errback);
};
// newContext中定义configure
configure: function (cfg) {
// 确保baseUrl以/结尾
if (cfg.baseUrl) {
if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
cfg.baseUrl += '/';
}
} var shim = config.shim,
objs = {
paths: true,
bundles: true,
config: true,
map: true
}; // 将cfg中的配置信息合并到默认的context的config中
eachProp(cfg, function (value, prop) {
if (objs[prop]) {
if (!config[prop]) {
config[prop] = {};
}
mixin(config[prop], value, true, true);
} else {
config[prop] = value;
}
}); //Reverse map the bundles
if (cfg.bundles) {
eachProp(cfg.bundles, function (value, prop) {
each(value, function (v) {
if (v !== prop) {
bundlesMap[v] = prop;
}
});
});
} // 对shim进行对应处理保存于默认context的config中
if (cfg.shim) {
eachProp(cfg.shim, function (value, id) {
// 规范化shim结构
// e.g. jquery-datepicker: ['jquery'] -> jquery-datepicker: {deps: ['jquery']}
if (isArray(value)) {
value = {
deps: value
};
}
// value.exports: 非requirejs定义文件的载入modulename,用于推断是否载入成功
// value.init: 是否存在初始化工作
if ((value.exports || value.init) && !value.exportsFn) {
value.exportsFn = context.makeShimExports(value);
}
shim[id] = value;
});
config.shim = shim;
}
...
}

综上:require.config将模块载入选项进行对应处理后,保存于默认的context.config中

require()

require()载入模块文件,

e.g.

require(['jquery'], function() {
...
});
req = requirejs = function (deps, callback, errback, optional) {

        var context, config,
contextName = defContextName; ... // 获取默认的context
context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
} ...
// 通过环境变量载入模块,newContext倒数几行代码中写到context.require = context.makeRequire()
return context.require(deps, callback, errback);
};
makeRequire: function (relMap, options) {
options = options || {}; function localRequire(deps, callback, errback) {
var id, map, requireMod; ... context.nextTick(function () {
// 将之前已define()定义的模块载入进来
intakeDefines();
// 创建require中默认的Module
requireMod = getModule(makeModuleMap(null, relMap)); ... // 初始化require信息,载入deps依赖项
requireMod.init(deps, callback, errback, {
enabled: true
});
// 根据config中waitSeconds来检查js载入是否超时。waitSeconds为0,无载入超时机制
// checkLoaded採用setTimeout,若未载入完即50ms后检查再次调用checkLoaded推断是否载入完成
checkLoaded();
}); return localRequire;
}
// Module中init的定义
init: function (depMaps, factory, errback, options) { ... if (options.enabled || this.enabled) {
// 将deps依赖项进行处理变为Module,进行载入
this.enable();
} else {
this.check();
}
}
enable: function () {
// 对depMaps的全部依赖项进行处理
each(this.depMaps, bind(this, function (depMap, i) {
var id, mod, handler; if (typeof depMap === 'string') { ... on(depMap, 'defined', bind(this, function (depExports) {
if (this.undefed) {
return;
}
// 跟进defineDep能够看到。将dep.exports保存在this.depExports中。用于载入后回调函数的參数传递,这是按顺序来的,回调函数调用详见以下check()中
this.defineDep(i, depExports);
this.check();
})); ... // 检查每一个Module是否初始化、载入完模块
this.check();
}
check: function () {
if (!this.enabled || this.enabling) {
return;
} var err, cjsModule,
id = this.map.id,
depExports = this.depExports,
exports = this.exports,
factory = this.factory; if (!this.inited) {
// 获取模块所对应的js文件,源代码跟踪fetch()发现实际是调用了context.load即req.load
this.fetch();
} else if (this.error) {
this.emit('error', this.error);
} else if (!this.defining) {
// deps依赖项已载入完成。准备回调
if (this.depCount < 1 && !this.defined) {
if (isFunction(factory)) { // 通过context.execCb调用回调函数,将刚保存的depExports的參数传入回调函数中。完成模块的依赖注入
if ((this.events.error && this.map.isDefine) ||
req.onError !== defaultOnError) {
try {
exports = context.execCb(id, factory, depExports, exports);
} catch (e) {
err = e;
}
} else {
exports = context.execCb(id, factory, depExports, exports);
}
} ...
req.load = function (context, moduleName, url) {
var config = (context && context.config) || {},
node;
// 是浏览器还是Webworker
if (isBrowser) {
// 通过 document.createElement('script')创建script节点,同一时候设置node.async = true实现异步载入
node = req.createNode(config, moduleName, url); node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
// 根据的浏览器的不同加入script的load、error事件
if (node.attachEvent && !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
!isOpera) { useInteractive = true; node.attachEvent('onreadystatechange', context.onScriptLoad);
} else {
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
}
// 设置script的src
node.src = url; currentlyAddingScript = node;
if (baseElement) {
head.insertBefore(node, baseElement);
} else {
head.appendChild(node);
}
currentlyAddingScript = null; return node;
} else if (isWebWorker) {
try {
// WebWorker採用importScripts载入js文件
importScripts(url); context.completeLoad(moduleName);
} catch (e) {
context.onError(makeError('importscripts',
'importScripts failed for ' +
moduleName + ' at ' + url,
e,
[moduleName]));
}
}
}

综上:require()就是对须要的依赖模块进行对应的url、是否在path、或存在shim等的处理后转换为Module。

载入js文件有以下两种方式:

Browser:document.createElement创建script,然后通过设置asyn=true。head.appendChild(node)

WebWorker:importScripts(url)

define()

define = function (name, deps, callback) {
var node, context; // 对实參不同情况做对应处理
... if (!deps && isFunction(callback)) {
deps = [];
// 通过toString()然后正則表達式将define()定义的依赖项保存在deps中
if (callback.length) {
callback
.toString()
.replace(commentRegExp, '')
.replace(cjsRequireRegExp, function (match, dep) {
deps.push(dep);
}); ...
}
} ... // 将define定义的信息存放在默认context.defQueue或globalDefQueue中,等定义define()的js文件载入完后。在通过来处理这些define信息
// 上面require()中js文件载入过程分析。可知js文件载入后将会调用事件函数onScriptLoad
(context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
}
onScriptLoad: function (evt) {
// 对文件载入的状态进行推断
if (evt.type === 'load' ||
(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { interactiveScript = null; // 将script的加入的事件函数进行清除,同一时候获取node的信息
var data = getScriptData(evt);
// 通过默认context来载入该data所对应的Module
context.completeLoad(data.id);
}
}
completeLoad: function (moduleName) {
var found, args, mod,
shim = getOwn(config.shim, moduleName) || {},
shExports = shim.exports; // 将globalDefQueue合并到context.defQueue中
takeGlobalQueue(); // 找到moduleName所对应的的Module
while (defQueue.length) {
args = defQueue.shift();
if (args[0] === null) {
args[0] = moduleName; if (found) {
break;
}
found = true;
} else if (args[0] === moduleName) {
found = true;
} // 获取args模块
callGetModule(args);
} ...
}
function callGetModule(args) {
// 若该模块没有被载入。即载入该模块,然后调用Module.init(),这个过程在require()已分析,即初始化、载入模块了
if (!hasProp(defined, args[0])) {
getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
}
}

综上:define()模块定义,仅将模块信息载入到默认context.defQueue或者globalDefQueue中,而处理这些信息是在定义define()的js文件载入完后进行的。

总体过程

  • 载入require.js库文件,生产默认的context
  • 查找data-main入口点,require.config()将模块载入选项做对应处理,然后加入默认的context中
  • 载入require()依赖项,通过document.createElement和设置asyn或者importScripts来载入js文件
  • 载入过程中发现define()模块定义。将模块定义信息载入到默认的context.defQueue或者globalDefQueue中,等定义define()的js文件载入后再处理这些模块定义信息

    当中:

  • js文件若未载入成功,採用setTimeout 50ms方式来循环推断是否载入完成

  • define()的依赖项提取採用function.toString() + 正則表達式
  • url的转换、Module的生产等处理过程可详见源代码

require.js源代码解密完成,是不是认为原来就这样实现的= =

事实上非常多事情并没有那么难。仅仅要你去分析它就会清楚非常多

小小吐槽:

require.js源代码代码规范、结构等感觉有点乱…

看过后仅仅想说”呵 呵”

解密javascript模块载入器require.js的更多相关文章

  1. JavaScript模块载入框架sea.js 学习一

    简单总结sea.js 学习 文件文件夹结构   /sea/sea.js      下载地址  http://seajs.org/docs/#downloads   /sea/jquery-sea.js ...

  2. JavaScript中模块化工具require.js

    什么是require.js? RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一.它还同时可以和其他的框架协同工作,使用RequireJS必将使您的前端代 ...

  3. Javascript模块化编程:require.js的用法

    摘自:http://blog.jobbole.com/30046/ 这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战. 我采用的是一个非常流行的库 ...

  4. Javascript模块化编程(三)require.js的用法及功能介绍

    这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战.我采用的是一个非常流行的库require.js感兴趣的朋友可以了解下啊 我采用的是一个非常流行的 ...

  5. Javascript模块化编程-require.js

    转自:https://www.cnblogs.com/digdeep/p/4607131.html Javascript模块化编程(一):模块的写法 随着网站逐渐变成"互联网应用程序&quo ...

  6. JavaScript模块化编程之require.js与sea.js

    为什么要模块化:当今,网站以不再是一个简单的页面,JavaScript也不再是做一些简单的脚本验证,随着WEB.20时代到来,前端工程师面临的必将是越来越庞大的JavaScript代码,越来越复杂的内 ...

  7. javascript模块化编程库require.js的用法

    随着javascript的兴起,越来越多的公司开始将JS模块化,以增加开发的效率和减少重复编写代码的.更是为了能更加容易的维护日后的代码,因为现在的随着人们对交互效果的越来越强烈的需求,我们的JS代码 ...

  8. Javascript模块化工具require.js教程

    转自:http://www.w3cschool.cc/w3cnote/requirejs-tutorial-1.html, http://www.w3cschool.cc/w3cnote/requir ...

  9. Javascript模块化编程(三):require.js的用法

    Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...

随机推荐

  1. MySQL----基本数据类型

    1.数值型: tinyint:1字节 smallint:2字节 mediumint:3字节 int:4字节 bigint:8字节 float:4字节 double:8字节 decimal(m,c):m ...

  2. 传智播客C/C++学员荣膺微软&Cocos 2d-x黑客松最佳创新奖

     6月30日,历时32小时的微软开放技术Cocos 2d-x 编程黑客松在北京望京微软大厦成功落下帷幕,这是微软开放技术首次联合Cocos 2d-x 在中国举办黑客松.此次活动共有包括传智播客C/ ...

  3. CentOS Linux 中文输入法安装及设置

    安装: 1.需要root权限,所以要用root登录 ,或su root 2.yum install "@Chinese Support" 3.exit 4.回到桌面,system- ...

  4. zoj2588 Burning Bridges(无向图的桥)

    题目请戳这里 题目大意:给一张无向图,现在要去掉一些边,使图仍然连通,求不能去掉的边. 题目分析:就是求无向图的桥. tarjan算法跑一遍,和无向图割点十分类似,这里要找low[v] > df ...

  5. linux安装mongodb并启动

    CentOS6.4 安装MongoDB   1.下载MongoDB(64位) http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.9.tg ...

  6. windows更改DNS设置

    浏览器解析域名时,首先在本地的host文件中查找记录, HOSTS文件记录的地址在: 将注释去掉 访问ysp.tlmall.com 会访问127.0.0.1

  7. windows 环境怎样恢复 (oracle 11g grid) ocr voting 损坏的集群

     windows 环境怎样恢复 (oracle 11g grid) ocr voting 损坏的集群 oracle 11g 以后 ocr 能够放到 asm 磁盘上,而ASM的启动依赖于ocr和vo ...

  8. Android 它们的定义View (一)

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 非常Android入门程序员AndroidView.可能都是比較恐 ...

  9. 自学SQL语言的例子(使用MySQL实现)

    SQL语言作为一种数据库管理的标准语言有着极为广泛的应用场景,菜鸟入门选用的数据库软件是轻量级的免费(这个极为重要)的MySQL,下载链接如下:http://www.mysql.com/downloa ...

  10. Android常见布局问题

    原文链接:http://www.cnblogs.com/Birdmafly/p/3809802.html 好久没写博了,因为最近在忙着做一个app,实在是没有时间.现在快完工了.想着还是把这个布局问题 ...