詳細源代碼一共就2000多行,來看我這篇分析的同窗應該都下載下來了,好了,話很少說,開始:javascript
代碼的開頭就出現3個全局變量: requirejs, require, definehtml
var requirejs, require, define; (function(global, setTimeout){ balababla...... })(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)))
require 和 define 你們應該都知道上幹什麼的,說實話,我是不知道的,在分析代碼的時候,我歷來也沒用過這個框架,就聽過AMD,就來直接看源碼了。java
若是你也不是很清楚,這2個變量是幹什麼的,我就來簡單介紹一下,懂得的同窗要是發現我說錯了,但願指點我也一下。windows
主頁面 index.html:瀏覽器
注意src是引入咱們的requirejs庫, data-main:就是咱們第一次用requrie的地方:閉包
<html> <head> <title></title> </head> <body> <script src="require.js" type="text/javascript" data-main="main.js"></script> </body> </html>
main.js:app
這裏2個代碼塊都是依賴require的:框架
(1)require.config:配置函數
(2)requrie(); 加載須要的函數,注意裏面的 ['name', 'say'],其實都是文件名,它們都在./js/ 目錄下,具體看conifgrequirejs
require.config({ baseUrl: '', paths: { 'nameDep': 'js/nameDep', 'say': 'js/say', 'name': 'js/name' }, shim: { 'name': { deps: ['nameDep'] } } }); require(['name', 'say'], function (name, say) { say(name); });
./js/name.js 和 ./js/say.js
//name define([''], function () { return '測試'; });
//say define([], function () { return function (name) { console.log(name); }; });
最後注意在config中有個skim,這裏面也是定義js文件的,只是因爲他可能不符合AMD加載的規範
./js/nameDep.js
console.log("nameDep.js")
以上分析,咱們大概看得出 require 和 define 都是 function
--------------------------------------分割線-------------------------------------------------------------------------
下面進入2000多行匿名函數的講解:
首先說一下2個參數: global 和 setTimeout
(function (global, setTimeout) { })(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout))
若是在瀏覽器的環境中,就是平時咱們所知的 windows對象 和 windows.setTimeout方法 。本文只討論瀏覽器環境,別的環境咱不考慮。
接下來就是一大堆的變量和函數的定義,太多了!反正我就掃了一眼,記不住?無所謂,只須要記住這開頭這3個變量就好了:req,contexts,cfg
2個是空對象 cfg,contexts ,還有一個函數 req 。
爲何它是函數??? 你一拉到底,必定會看到 req = requirejs = function(){} 的定義,眼神很差也不要緊,看我下面的代碼框裏有req({});
var req, .... contexts = {}, cfg = {}, .... //各類function定義 .... //Create default context. req({}); .....
好了把眼神定位到 req({})吧, 由於這是上面一大堆定義後,第一次執行了代碼!!!!
req這個函數長什麼樣子?? 你用文字匹配往上找:
/** * Main entry point. * * If the only argument to require is a string, then the module that * is represented by that string is fetched for the appropriate context. * * If the first argument is an array, then it will be treated as an array * of dependency string names to fetch. An optional function callback can * be specified to execute when all of those dependencies are available. * * Make a local req variable to help Caja compliance (it assumes things * on a require that are not standardized), and to give a short * name for minification/local scope use. */ req = requirejs = function (deps, callback, errback, optional) { //Find the right context, use default var context, config, contextName = defContextName; //開頭定義了 defContextName = "_" // Determine if have config object in the call. if (!isArray(deps) && typeof deps !== 'string') { // deps is a config object config = deps; if (isArray(callback)) { // Adjust args if there are dependencies deps = callback; callback = errback; errback = optional; } else { deps = []; } } //第一次沒有config.context 跳過 if (config && config.context) { contextName = config.context; } //第一次context == undefined context = getOwn(contexts, contextName); //第一次進入 if (!context) { //只有第一次會調用newContext("_") context = contexts[contextName] = req.s.newContext(contextName); } if (config) { context.configure(config); } return context.require(deps, callback, errback); };
上面代碼 context出現的頻率很是高,說明這小子很重要。那咱們來看看這小子究竟是啥!!!
直接找到它的定義:
context = contexts[contextName] = req.s.newContext(contextName);
那 newContext 又是什麼鬼! 搜它!!!!!
function newContext(contextName) { var context ... //一大堆function定義 .... context={ ..... } context.require = context.makeRequire(); return context; }
用20秒的時間掃一眼!老套路,一大堆的變量,函數的定義。 一直到最後return 一個 context變量。
再往上拉看看 context 究竟是個什麼鬼!!
記憶力沒毛病的話,發現context對象裏裝的全是剛纔定義過一些變量,函數。
-----------------------------------------------------------------------------------------------
| 其中return的上一行代碼: |
| context.require = context.makeRequire(); |
| 有興趣就去看一眼,很簡單,就是返回一個叫localRequire的閉包 |
------------------------------------------------------------------------------------------------
OK,這個newContext函數咱們已經差很少了解了,就是返回一個對象,經過這個對象控制newContext裏的一系列私有變量和對象。
咱們的contexts就以下圖所示:
接下去走
context.configure(config);
我建議你去看一下,由於裏面啥也沒幹,你只須要花10秒左右的時間就能夠看完。。。
而後走
return context.require(deps, callback, errback);
看上面的圖,context.require 就是 localRequire
接下來,咱們走進localRequire的世界裏一探究竟!!!!其中deps = [ ]
function localRequire(deps, callback, errback) { var id, map, requireMod; if (options.enableBuildCallback && callback && isFunction(callback)) { callback.__requireJsBuild = true; } if (typeof deps === 'string') { if (isFunction(callback)) { //Invalid call return onError(makeError('requireargs', 'Invalid require call'), errback); } //If require|exports|module are requested, get the //value for them from the special handlers. Caveat: //this only works while module is being defined. if (relMap && hasProp(handlers, deps)) { return handlers[deps](registry[relMap.id]); } //Synchronous access to one module. If require.get is //available (as in the Node adapter), prefer that. if (req.get) { return req.get(context, deps, relMap, localRequire); } //Normalize module name, if it contains . or .. map = makeModuleMap(deps, relMap, false, true); id = map.id; if (!hasProp(defined, id)) { return onError(makeError('notloaded', 'Module name "' + id + '" has not been loaded yet for context: ' + contextName + (relMap ? '' : '. Use require([])'))); } return defined[id]; } //Grab defines waiting in the global queue. intakeDefines(); //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(); }); return localRequire; }
很快咱們就把目光鎖定到(由於,前面的一大段代碼都是if語句,不符合條件進不去)
//Grab defines waiting in the global queue. intakeDefines();
intakeDefines --》takeGlobalQueue--》context.nextTick--》intakeDefines --》 requireMod.init --》 checkLoaded();
裏面大體就是上面所示,由於有點複雜,我這裏暫時先不說,接續走簡單的。。
到此爲止,咱們已經走完了一邊req。
大體流程以下圖所示:
接下去,咱們再一拉到底!!!!
倒數第二行:
//Set up with config info. req(cfg);
cfg還記得是什麼嗎?? 一開始是定義爲空對象啊!!!
有地方修改過它嗎? 往上拉一拉!!
//Look for a data-main script attribute, which could also adjust the baseUrl. if (isBrowser && !cfg.skipDataMain) { //Figure out baseUrl. Get it from the script tag with require.js in it. eachReverse(scripts(), function(script) { //Set the 'head' where we can append children by //using the script's parent. if (!head) { head = script.parentNode; } //Look for a data-main attribute to set main script for the page //to load. If it is there, the path to data main becomes the //baseUrl, if it is not already set. dataMain = script.getAttribute('data-main'); if (dataMain) { //Preserve dataMain in case it is a path (i.e. contains '?') mainScript = dataMain; //Set final baseUrl if there is not already an explicit one, //but only do so if the data-main value is not a loader plugin //module ID. if (!cfg.baseUrl && mainScript.indexOf('!') === -1) { //Pull off the directory of data-main for use as the //baseUrl. src = mainScript.split('/'); mainScript = src.pop(); subPath = src.length ? src.join('/') + '/' : './'; cfg.baseUrl = subPath; } //Strip off any trailing .js since mainScript is now //like a module name. mainScript = mainScript.replace(jsSuffixRegExp, ''); //If mainScript is still a path, fall back to dataMain if (req.jsExtRegExp.test(mainScript)) { mainScript = dataMain; } //Put the data-main script in the files to load. cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript]; return true; } }); }
這裏面作的事情很簡單,就是找到頁面中包含 "data-main" 屬性的script標籤,分析路徑。
最後獲得這樣的結果:
好了。再回到req(cfg);
這一次咱們好好的走一走上面流程圖裏的步驟。。。。
把目光迅速鎖定到:
return context.require(deps, callback, errback);