前言
現在工作中基本離不開requireJS這種模塊管理工具了,之前一直在用,但是對其原理不甚熟悉,整兩天我們來試着學習其源碼,而后在探尋其背后的AMD思想吧
於是今天的目標是熟悉requireJS整體框架結構,順便看看之前的簡單demo
程序入口
源碼閱讀仍然有一定門檻,通看的做法不適合我等素質的選手,所以還是得由入口開始,requireJS的入口便是引入時候指定的data-main
<script src="require.js" type="text/javascript" data-main="main.js"></script>
在js引入后,會自動執行指向data-main的js函數,這個就是我們所謂的入口,跟着這條線,我們就進入了requirejs的大門
首先,引入js文件本身不會干什么事情,那么requirejs內部做了什么呢?
① 除了一些初始化操作以為第一件干的事情,值執行這段代碼:
//Create default context. req({});
這段代碼會構造默認的參數,其調用的又是整個程序的入口
req = requirejs = function (deps, callback, errback, optional) {}
這里具體干了什么我們先不予關注,繼續往后面走,因為貌似,這里與data-main暫時不相干,因為這段會先於data-main邏輯運行
然后,進入data-main相關的邏輯了:
//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. if (!cfg.baseUrl) { //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; } }); }
因為requireJS不止用於瀏覽器,所以這里有一個判斷,我們暫時不予關注,看看他干了些什么
① 他會去除頁面所有的script標簽,然后倒敘遍歷之
scripts() => [<script src="require.js" type="text/javascript" data-main="main.js"></script>]
這個地方遇到兩個方法
eachReverse
與each一致,只不過由逆序遍歷
function eachReverse(ary, func) { if (ary) { var i; for (i = ary.length - 1; i > -1; i -= 1) { if (ary[i] && func(ary[i], i, ary)) { break; } } } }
scripts
便是document.getElementsByTagName('script');返回所有的script標簽
然后開始的head便是html中的head標簽,暫時不予理睬
if (isBrowser) { head = s.head = document.getElementsByTagName('head')[0]; //If BASE tag is in play, using appendChild is a problem for IE6. //When that browser dies, this can be removed. Details in this jQuery bug: //http://dev.jquery.com/ticket/2709 baseElement = document.getElementsByTagName('base')[0]; if (baseElement) { head = s.head = baseElement.parentNode; } }
dataMain = script.getAttribute('data-main');
然后這一句便可以獲取當前指定運行的文件名,比如這里
dataMain => main.js
如果不存在就不會有什么操作了
PS:我原來記得默認指向main.js,看來是我記錯了......
然后下來做了一些處理,會根據指定的main.js初步確定bashUrl,其實就是與main.js統一目錄
最后做了關鍵的一個步驟:
cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];

將main放入帶加載的配置中,而本身不干任何事情,繼續接下來的邏輯......然后此邏輯暫時結束,根據這些參數進入下一步驟
req/requirejs
根據上一步驟的處理,會形成上面截圖的參數,而后再一次執行入口函數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; // 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 = []; } } if (config && config.context) { contextName = config.context; } context = getOwn(contexts, contextName); if (!context) { context = contexts[contextName] = req.s.newContext(contextName); } if (config) { context.configure(config); } return context.require(deps, callback, errback); };
這個時候我們的第一個參數deps就不再是undefined了,而是一個對象,這里便將其配置放到了config變量中保持deps為一數組,然后干了些其他事情
這里有個變量context,需要特別注意,后面我們來看看他有些什么,這里有一個新的函數
function getOwn(obj, prop) { return hasProp(obj, prop) && obj[prop]; } function hasProp(obj, prop) { return hasOwn.call(obj, prop); } hasOwn = op.hasOwnProperty
這里會獲取非原型屬性將其擴展,首次執行時候會碰到一個非常重要的函數newContext 因為他是一個核心,我們這里暫時選擇忽略,不然整個全部就陷進去了
經過newContext處理后的context就變成這個樣子了:

if (config) { context.configure(config); }
這里就會將我們第一步的參數賦值進對象,具體干了什么,我們依舊不予理睬,main.js干了兩件事情:
① 暫時性設置了baseUrl
② 告訴requireJS你馬上要加載我了
於是最后終於調用require開始處理邏輯
return context.require(deps, callback, errback);
require
因為context.require = context.makeRequire();而該函數本身又返回localRequire函數,所以事實上這里是執行的localRequire函數,內部維護着一個閉包
因為nextContext只會運行一次,所以很多require實際用到的變量都是nextContext閉包所維護,比如我們這里便可以使用config變量

這里依舊有一些特殊處理,比如deps是字符串的情況,但是我們暫時不予關注.......
PS:搞了這么久很多不予關注了,欠了很多帳啊!
他這里應該是有一個BUG,所以這里用到了一個settimeout延時
PS:因為settimeout的使用,整個這塊的程序全部會拋到主干邏輯之后了
然后接下來的步驟比較關鍵了,我們先拋開一切來理一理這個newContext
newContext
newContext占了源碼的主要篇幅,他也只會在初始化時候執行一次,而后便不再執行了:
if (!context) { context = contexts[contextName] = req.s.newContext(contextName); }
現在,我們就目前而知來簡單理一理,requireJS的結構
① 變量聲明,工具類
在newContext之前,完全是做一些變量的定義,或者做一些簡單的操作,里面比較關鍵的是contexts/cfg對象,會被后面無數次的用到
② 實例化上下文/newContext
緊接着就是newContext這洋洋灑灑一千多行代碼了,其中主要干了什么暫時不知道,據我觀察應該是做環境相關的准備
③ 對外接口
上面操作結束后便提供了幾個主要對外接口
requirejs
require.config
雖然這里是兩個函數,其實都是requirejs這一關入口
而后,require自己擼了一把,實例化了默認的參數,這里便調用了newContext,所以以后都不會調用,其中的函數多處於其閉包環境
接下來根據引入script標簽的data-main做了一次文章,初始化了簡單的參數,並將main.js作為了依賴項,這里會根據main.js重寫cfg對象
最后requirejs執行一次reg(cfg),便真的開始了所有操作,這個時候我們就進入newContext,看看他主要干了什么
PS:所有require並未提供任何借口出來,所以在全局想查看其contexts或者cfg是不行的,而且每次操作都可能導致其改變
要了解newContext函數,還是需要進入其入口
if (!context) { context = contexts[contextName] = req.s.newContext(contextName); }
從script標簽引入require庫時候,會因為這段代碼執行一次newContext函數,從此后,該函數不會被執行,其實現的原因不是我等現在能明白的,先看懂實現再說吧
//Create default context. req({});
所以上面說了那么多,看了這么久,其實最關鍵的還是首次加載,首次加載就決定了運行上下文了
整體結構

newContext的基本結構大概是這樣:
① 函數作用域內變量定義(中間初始化了一發handlers變量)
② 一堆工具函數定義
③ Module模塊(這塊給人的感覺不明覺厲...應該是核心吧)
④ 實例化context對象,將該對象返回,然后基本結束
進入newContext后,第一步是基本變量定義,這種對外的框架一般都不會到處命名變量,而是將所有變量全部提到函數最前面
一來是js解析時候聲明本身會提前,而來可能是到處命名變量會讓我們找不到吧......
開始定義了很多變量,我們一來都不知道是干神馬的,但是config變量卻引起了我們的注意,這里先放出來,繼續往下就是一連串的函數了,值得說明的是,這些變量會被重復利用哦
一眼看下來,該函數本身並沒有做什么實際的事情,這個時候我們就需要找其入口,這里的入口是
//首次調用 req({}) => //觸發newContext,做首次初始化並返回給context對象 context = contexts[contextName] = req.s.newContext(contextName) => //注意這里require函數其實處於了mackRequire函數的閉包環境 context.require = context.makeRequire(); => //首次調用newContext返回對象初始化變量 context.configure(config);
所以,在首次初始化后,並未做特別的處理,直到configure的調用,於是讓我們進入該函數
/** * Set a configuration for the context. * @param {Object} cfg config object to integrate. */ configure: function (cfg) { //Make sure the baseUrl ends in a slash. if (cfg.baseUrl) { if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { cfg.baseUrl += '/'; } } //Save off the paths and packages since they require special processing, //they are additive. var pkgs = config.pkgs, shim = config.shim, objs = { paths: true, config: true, map: true }; eachProp(cfg, function (value, prop) { if (objs[prop]) { if (prop === 'map') { if (!config.map) { config.map = {}; } mixin(config[prop], value, true, true); } else { mixin(config[prop], value, true); } } else { config[prop] = value; } }); //Merge shim if (cfg.shim) { eachProp(cfg.shim, function (value, id) { //Normalize the structure if (isArray(value)) { value = { deps: value }; } if ((value.exports || value.init) && !value.exportsFn) { value.exportsFn = context.makeShimExports(value); } shim[id] = value; }); config.shim = shim; } //Adjust packages if necessary. if (cfg.packages) { each(cfg.packages, function (pkgObj) { var location; pkgObj = typeof pkgObj === 'string' ? { name: pkgObj} : pkgObj; location = pkgObj.location; //Create a brand new object on pkgs, since currentPackages can //be passed in again, and config.pkgs is the internal transformed //state for all package configs. pkgs[pkgObj.name] = { name: pkgObj.name, location: location || pkgObj.name, //Remove leading dot in main, so main paths are normalized, //and remove any trailing .js, since different package //envs have different conventions: some use a module name, //some use a file name. main: (pkgObj.main || 'main') .replace(currDirRegExp, '') .replace(jsSuffixRegExp, '') }; }); //Done with modifications, assing packages back to context config config.pkgs = pkgs; } //If there are any "waiting to execute" modules in the registry, //update the maps for them, since their info, like URLs to load, //may have changed. eachProp(registry, function (mod, id) { //If module already has init called, since it is too //late to modify them, and ignore unnormalized ones //since they are transient. if (!mod.inited && !mod.map.unnormalized) { mod.map = makeModuleMap(id); } }); //If a deps array or a config callback is specified, then call //require with those args. This is useful when require is defined as a //config object before require.js is loaded. if (cfg.deps || cfg.callback) { context.require(cfg.deps || [], cfg.callback); } },
首次傳入的是空對象,所以開始一段代碼暫時沒有意義,這里使用的config變量正是newContext維護的閉包,也就是上面讓注意的
config = {
//Defaults. Do not set a default for map
//config to speed up normalize(), which
//will run faster if there is no default.
waitSeconds: 7,
baseUrl: './',
paths: {},
pkgs: {},
shim: {},
config: {}
},
下面用到了一個新的函數:
eachProp
這個函數會遍歷對象所有非原型屬性,並且使用第二個參數(函數)執行之,如果返回true便停止,首次執行時候cfg為空對象,便沒有往下走,否則config變量會被操作,具體我們暫時不管
/** * Cycles over properties in an object and calls a function for each * property value. If the function returns a truthy value, then the * iteration is stopped. */ function eachProp(obj, func) { var prop; for (prop in obj) { if (hasProp(obj, prop)) { if (func(obj[prop], prop)) { break; } } } }
這個所謂的入口執行后實際的意義基本等於什么都沒有干......
但是,這里可以得出一個弱弱的結論就是
configure是用於設置參數滴
所以所謂的入口其實沒有干事情,這個時候第二個入口便出現了
context.require
return context.require(deps, callback, errback);
參數設置結束后便會執行context的require方法,這個是真正的入口,他實際調用順序為:
context.require = context.makeRequire(); => localRequire
所以真正調用localRequire時候,已經執行了一番makeRequire函數了,現在處於了其上下文,正因為localRequire被處理過,其多了幾個函數屬性
除此之外,暫時沒有看出其它變化,所以這里在某些特定場景是等價的
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; }
過程中會執行一次intakeDefines,他的意義是定義全局隊列,其意義暫時不明,然后進入了前面說的那個settimeout
在主干邏輯結束后,這里會進入時鍾隊列的回調,其中的代碼就比較關鍵了,只不過首次不能體現
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(); });
這段代碼事實上是比較奇特的,他會完全脫離整個require代碼,比如整個
return context.require(deps, callback, errback);
執行了后上面才會慢慢執行
PS:require這段比較重要,留待明天分析,今天先看整體邏輯
下面的主要邏輯又到了這里
requireMod = getModule(makeModuleMap(null, relMap));
我們這里主要先看getModule先,首先makeModuleMap比較關鍵,他會根據規則創建一些模塊唯一標識的東西,暫時是什么當然是先不管啦......
PS:其規則應該與加載的require數量有關,最后會形成這個東西

/** * Creates a module mapping that includes plugin prefix, module * name, and path. If parentModuleMap is provided it will * also normalize the name via require.normalize() * * @param {String} name the module name * @param {String} [parentModuleMap] parent module map * for the module name, used to resolve relative names. * @param {Boolean} isNormalized: is the ID already normalized. * This is true if this call is done for a define() module ID. * @param {Boolean} applyMap: apply the map config to the ID. * Should only be true if this map is for a dependency. * * @returns {Object} */ function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { var url, pluginModule, suffix, nameParts, prefix = null, parentName = parentModuleMap ? parentModuleMap.name : null, originalName = name, isDefine = true, normalizedName = ''; //If no name, then it means it is a require call, generate an //internal name. if (!name) { isDefine = false; name = '_@r' + (requireCounter += 1); } nameParts = splitPrefix(name); prefix = nameParts[0]; name = nameParts[1]; if (prefix) { prefix = normalize(prefix, parentName, applyMap); pluginModule = getOwn(defined, prefix); } //Account for relative paths if there is a base name. if (name) { if (prefix) { if (pluginModule && pluginModule.normalize) { //Plugin is loaded, use its normalize method. normalizedName = pluginModule.normalize(name, function (name) { return normalize(name, parentName, applyMap); }); } else { normalizedName = normalize(name, parentName, applyMap); } } else { //A regular module. normalizedName = normalize(name, parentName, applyMap); //Normalized name may be a plugin ID due to map config //application in normalize. The map config values must //already be normalized, so do not need to redo that part. nameParts = splitPrefix(normalizedName); prefix = nameParts[0]; normalizedName = nameParts[1]; isNormalized = true; url = context.nameToUrl(normalizedName); } } //If the id is a plugin id that cannot be determined if it needs //normalization, stamp it with a unique ID so two matching relative //ids that may conflict can be separate. suffix = prefix && !pluginModule && !isNormalized ? '_unnormalized' + (unnormalizedCounter += 1) : ''; return { prefix: prefix, name: normalizedName, parentMap: parentModuleMap, unnormalized: !!suffix, url: url, originalName: originalName, isDefine: isDefine, id: (prefix ? prefix + '!' + normalizedName : normalizedName) + suffix }; }
然后是我們關鍵的getModule函數
function getModule(depMap) { var id = depMap.id, mod = getOwn(registry, id); if (!mod) { mod = registry[id] = new context.Module(depMap); } return mod; }
可以看到,一旦我們加載了一個模塊便不會重新加載了,這是一個很重要的發現哦
registry
該全局變量用於存儲加載模塊的鍵值對
第一步當然是加載啦,但是首次應該會跳過,因為當然事實上沒有需要加載的模塊,一起跟下去吧
Module
然后進入我們關鍵的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 */ }; Module.prototype = { init: function (depMaps, factory, errback, options) { options = options || {}; //Do not do more inits if already done. Can happen if there //are multiple define calls for the same module. That is not //a normal, common case, but it is also not unexpected. if (this.inited) { return; } this.factory = factory; if (errback) { //Register for errors on this module. this.on('error', errback); } else if (this.events.error) { //If no errback already, but there are error listeners //on this module, set up an errback to pass to the deps. errback = bind(this, function (err) { this.emit('error', err); }); } //Do a copy of the dependency array, so that //source inputs are not modified. For example //"shim" deps are passed in here directly, and //doing a direct modification of the depMaps array //would affect that config. this.depMaps = depMaps && depMaps.slice(0); this.errback = errback; //Indicate this module has be initialized this.inited = true; this.ignore = options.ignore; //Could have option to init this module in enabled mode, //or could have been previously marked as enabled. However, //the dependencies are not known until init is called. So //if enabled previously, now trigger dependencies as enabled. if (options.enabled || this.enabled) { //Enable this module and dependencies. //Will call this.check() this.enable(); } else { this.check(); } }, defineDep: function (i, depExports) { //Because of cycles, defined callback for a given //export can be called more than once. if (!this.depMatched[i]) { this.depMatched[i] = true; this.depCount -= 1; this.depExports[i] = depExports; } }, fetch: function () { if (this.fetched) { return; } this.fetched = true; context.startTime = (new Date()).getTime(); var map = this.map; //If the manager is for a plugin managed resource, //ask the plugin to load it now. if (this.shim) { context.makeRequire(this.map, { enableBuildCallback: true })(this.shim.deps || [], bind(this, function () { return map.prefix ? this.callPlugin() : this.load(); })); } else { //Regular dependency. return map.prefix ? this.callPlugin() : this.load(); } }, load: function () { var url = this.map.url; //Regular dependency. if (!urlFetched[url]) { urlFetched[url] = true; context.load(this.map.id, url); } }, /** * Checks if the module is ready to define itself, and if so, * define it. */ 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) { this.fetch(); } else if (this.error) { this.emit('error', this.error); } else if (!this.defining) { //The factory could trigger another require call //that would result in checking this module to //define itself again. If already in the process //of doing that, skip this work. this.defining = true; if (this.depCount < 1 && !this.defined) { if (isFunction(factory)) { //If there is an error listener, favor passing //to that instead of throwing an error. However, //only do it for define()'d modules. require //errbacks should not be called for failures in //their callbacks (#699). However if a global //onError is set, use that. 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); } if (this.map.isDefine) { //If setting exports via 'module' is in play, //favor that over return value and exports. After that, //favor a non-undefined return value over exports use. cjsModule = this.module; if (cjsModule && cjsModule.exports !== undefined && //Make sure it is not already the exports value cjsModule.exports !== this.exports) { exports = cjsModule.exports; } else if (exports === undefined && this.usingExports) { //exports already set the defined value. exports = this.exports; } } if (err) { err.requireMap = this.map; err.requireModules = this.map.isDefine ? [this.map.id] : null; err.requireType = this.map.isDefine ? 'define' : 'require'; return onError((this.error = err)); } } else { //Just a literal value exports = factory; } this.exports = exports; if (this.map.isDefine && !this.ignore) { defined[id] = exports; if (req.onResourceLoad) { req.onResourceLoad(context, this.map, this.depMaps); } } //Clean up cleanRegistry(id); this.defined = true; } //Finished the define stage. Allow calling check again //to allow define notifications below in the case of a //cycle. this.defining = false; if (this.defined && !this.defineEmitted) { this.defineEmitted = true; this.emit('defined', this.exports); this.defineEmitComplete = true; } } }, callPlugin: function () { var map = this.map, id = map.id, //Map already normalized the prefix. pluginMap = makeModuleMap(map.prefix); //Mark this as a dependency for this plugin, so it //can be traced for cycles. this.depMaps.push(pluginMap); on(pluginMap, 'defined', bind(this, function (plugin) { var load, normalizedMap, normalizedMod, name = this.map.name, parentName = this.map.parentMap ? this.map.parentMap.name : null, localRequire = context.makeRequire(map.parentMap, { enableBuildCallback: true }); //If current map is not normalized, wait for that //normalized name to load instead of continuing. if (this.map.unnormalized) { //Normalize the ID if the plugin allows it. if (plugin.normalize) { name = plugin.normalize(name, function (name) { return normalize(name, parentName, true); }) || ''; } //prefix and name should already be normalized, no need //for applying map config again either. normalizedMap = makeModuleMap(map.prefix + '!' + name, this.map.parentMap); on(normalizedMap, 'defined', bind(this, function (value) { this.init([], function () { return value; }, null, { enabled: true, ignore: true }); })); normalizedMod = getOwn(registry, normalizedMap.id); if (normalizedMod) { //Mark this as a dependency for this plugin, so it //can be traced for cycles. this.depMaps.push(normalizedMap); if (this.events.error) { normalizedMod.on('error', bind(this, function (err) { this.emit('error', err); })); } normalizedMod.enable(); } return; } load = bind(this, function (value) { this.init([], function () { return value; }, null, { enabled: true }); }); load.error = bind(this, function (err) { this.inited = true; this.error = err; err.requireModules = [id]; //Remove temp unnormalized modules for this module, //since they will never be resolved otherwise now. eachProp(registry, function (mod) { if (mod.map.id.indexOf(id + '_unnormalized') === 0) { cleanRegistry(mod.map.id); } }); onError(err); }); //Allow plugins to load other code without having to know the //context or how to 'complete' the load. load.fromText = bind(this, function (text, textAlt) { /*jslint evil: true */ var moduleName = map.name, moduleMap = makeModuleMap(moduleName), hasInteractive = useInteractive; //As of 2.1.0, support just passing the text, to reinforce //fromText only being called once per resource. Still //support old style of passing moduleName but discard //that moduleName in favor of the internal ref. if (textAlt) { text = textAlt; } //Turn off interactive script matching for IE for any define //calls in the text, then turn it back on at the end. if (hasInteractive) { useInteractive = false; } //Prime the system by creating a module instance for //it. getModule(moduleMap); //Transfer any config to this other module. if (hasProp(config.config, id)) { config.config[moduleName] = config.config[id]; } try { req.exec(text); } catch (e) { return onError(makeError('fromtexteval', 'fromText eval for ' + id + ' failed: ' + e, e, [id])); } if (hasInteractive) { useInteractive = true; } //Mark this as a dependency for the plugin //resource this.depMaps.push(moduleMap); //Support anonymous modules. context.completeLoad(moduleName); //Bind the value of that module to the value for this //resource ID. localRequire([moduleName], load); }); //Use parentName here since the plugin's name is not reliable, //could be some weird string with no path that actually wants to //reference the parentName's path. plugin.load(map.name, localRequire, load, config); })); context.enable(pluginMap, this); this.pluginMaps[pluginMap.id] = pluginMap; }, enable: function () { enabledRegistry[this.map.id] = this; this.enabled = true; //Set flag mentioning that the module is enabling, //so that immediate calls to the defined callbacks //for dependencies do not trigger inadvertent load //with the depCount still being zero. this.enabling = true; //Enable each dependency each(this.depMaps, bind(this, function (depMap, i) { var id, mod, handler; if (typeof depMap === 'string') { //Dependency needs to be converted to a depMap //and wired up to this module. depMap = makeModuleMap(depMap, (this.map.isDefine ? this.map : this.map.parentMap), false, !this.skipMap); this.depMaps[i] = depMap; handler = getOwn(handlers, depMap.id); if (handler) { this.depExports[i] = handler(this); return; } this.depCount += 1; on(depMap, 'defined', bind(this, function (depExports) { this.defineDep(i, depExports); this.check(); })); if (this.errback) { on(depMap, 'error', bind(this, this.errback)); } } id = depMap.id; mod = registry[id]; //Skip special modules like 'require', 'exports', 'module' //Also, don't call enable if it is already enabled, //important in circular dependency cases. if (!hasProp(handlers, id) && mod && !mod.enabled) { context.enable(depMap, this); } })); //Enable each plugin that is used in //a dependency eachProp(this.pluginMaps, bind(this, function (pluginMap) { var mod = getOwn(registry, pluginMap.id); if (mod && !mod.enabled) { context.enable(pluginMap, this); } })); this.enabling = false; this.check(); }, on: function (name, cb) { var cbs = this.events[name]; if (!cbs) { cbs = this.events[name] = []; } cbs.push(cb); }, emit: function (name, evt) { each(this.events[name], function (cb) { cb(evt); }); if (name === 'error') { //Now that the error handler was triggered, remove //the listeners, since this broken Module instance //can stay around for a while in the registry. delete this.events[name]; } } };
總的來說,這個模塊還是很長的,首先是其構造函數

這里仍有很多東西讀不懂,所以就全部過吧,反正今天的主要目的是熟悉整體框架
這里實例化結束后便形成了一個模塊暫存於requireMod變量中,函數執行結束后變量會銷毀,該模塊會存與全局registery對象中
這里會執行其init方法干具體業務的事情
requireMod.init(deps, callback, errback, { enabled: true });
這里又會執行
this.enable();
enable: function () { enabledRegistry[this.map.id] = this; this.enabled = true; //Set flag mentioning that the module is enabling, //so that immediate calls to the defined callbacks //for dependencies do not trigger inadvertent load //with the depCount still being zero. this.enabling = true; //Enable each dependency each(this.depMaps, bind(this, function (depMap, i) { var id, mod, handler; if (typeof depMap === 'string') { //Dependency needs to be converted to a depMap //and wired up to this module. depMap = makeModuleMap(depMap, (this.map.isDefine ? this.map : this.map.parentMap), false, !this.skipMap); this.depMaps[i] = depMap; handler = getOwn(handlers, depMap.id); if (handler) { this.depExports[i] = handler(this); return; } this.depCount += 1; on(depMap, 'defined', bind(this, function (depExports) { this.defineDep(i, depExports); this.check(); })); if (this.errback) { on(depMap, 'error', bind(this, this.errback)); } } id = depMap.id; mod = registry[id]; //Skip special modules like 'require', 'exports', 'module' //Also, don't call enable if it is already enabled, //important in circular dependency cases. if (!hasProp(handlers, id) && mod && !mod.enabled) { context.enable(depMap, this); } })); //Enable each plugin that is used in //a dependency eachProp(this.pluginMaps, bind(this, function (pluginMap) { var mod = getOwn(registry, pluginMap.id); if (mod && !mod.enabled) { context.enable(pluginMap, this); } })); this.enabling = false; this.check(); },
然后又會調用 this.check();這個家伙操作結束后接下來checkLoaded就會創建script標簽了......
/** * Checks if the module is ready to define itself, and if so, * define it. */ 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) { this.fetch(); } else if (this.error) { this.emit('error', this.error); } else if (!this.defining) { //The factory could trigger another require call //that would result in checking this module to //define itself again. If already in the process //of doing that, skip this work. this.defining = true; if (this.depCount < 1 && !this.defined) { if (isFunction(factory)) { //If there is an error listener, favor passing //to that instead of throwing an error. However, //only do it for define()'d modules. require //errbacks should not be called for failures in //their callbacks (#699). However if a global //onError is set, use that. 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); } if (this.map.isDefine) { //If setting exports via 'module' is in play, //favor that over return value and exports. After that, //favor a non-undefined return value over exports use. cjsModule = this.module; if (cjsModule && cjsModule.exports !== undefined && //Make sure it is not already the exports value cjsModule.exports !== this.exports) { exports = cjsModule.exports; } else if (exports === undefined && this.usingExports) { //exports already set the defined value. exports = this.exports; } } if (err) { err.requireMap = this.map; err.requireModules = this.map.isDefine ? [this.map.id] : null; err.requireType = this.map.isDefine ? 'define' : 'require'; return onError((this.error = err)); } } else { //Just a literal value exports = factory; } this.exports = exports; if (this.map.isDefine && !this.ignore) { defined[id] = exports; if (req.onResourceLoad) { req.onResourceLoad(context, this.map, this.depMaps); } } //Clean up cleanRegistry(id); this.defined = true; } //Finished the define stage. Allow calling check again //to allow define notifications below in the case of a //cycle. this.defining = false; if (this.defined && !this.defineEmitted) { this.defineEmitted = true; this.emit('defined', this.exports); this.defineEmitComplete = true; } } },
然后今天累了,明天繼續吧......
結語
今天的目標是熟悉requireJS的整體結構,如果沒有錯覺或者解讀失誤,我們應該大概了解了requireJS的整體結構,於是讓我們明天繼續吧
PS:尼瑪這個框架還真是有點難,小釵感覺有點小吃力啊,估計要讀到下周才能真正理解一點的了........
