如今工做中基本離不開requireJS這種模塊管理工具了,以前一直在用,可是對其原理不甚熟悉,整兩天咱們來試着學習其源碼,然後在探尋其背後的AMD思想吧javascript
因而今天的目標是熟悉requireJS總體框架結構,順便看看以前的簡單demohtml
RequireJS學習筆記 java
源碼閱讀仍然有必定門檻,通看的作法不適合我等素質的選手,因此仍是得由入口開始,requireJS的入口即是引入時候指定的data-mainjquery
<script src="require.js" type="text/javascript" data-main="main.js"></script>
在js引入後,會自動執行指向data-main的js函數,這個就是咱們所謂的入口,跟着這條線,咱們就進入了requirejs的大門數組
首先,引入js文件自己不會幹什麼事情,那麼requirejs內部作了什麼呢?瀏覽器
① 除了一些初始化操做覺得第一件乾的事情,值執行這段代碼:bash
//Create default context. req({});
這段代碼會構造默認的參數,其調用的又是整個程序的入口閉包
req = requirejs = function (deps, callback, errback, optional) {}
這裏具體幹了什麼咱們先不予關注,繼續日後面走,由於貌似,這裏與data-main暫時不相干,由於這段會先於data-main邏輯運行app
而後,進入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>]
這個地方遇到兩個方法
與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; } } } }
即是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,這個時候就會發生不同的事情了
/** * 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);
由於context.require = context.makeRequire();而該函數自己又返回localRequire函數,因此事實上這裏是執行的localRequire函數,內部維護着一個閉包
由於nextContext只會運行一次,因此不少require實際用到的變量都是nextContext閉包所維護,好比咱們這裏即可以使用config變量
這裏依舊有一些特殊處理,好比deps是字符串的狀況,可是咱們暫時不予關注.......
PS:搞了這麼久不少不予關注了,欠了不少賬啊!
他這裏應該是有一個BUG,因此這裏用到了一個settimeout延時
PS:由於settimeout的使用,整個這塊的程序所有會拋到主幹邏輯以後了
而後接下來的步驟比較關鍵了,咱們先拋開一切來理一理這個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: {} },
下面用到了一個新的函數:
這個函數會遍歷對象全部非原型屬性,而且使用第二個參數(函數)執行之,若是返回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是用於設置參數滴
因此所謂的入口其實沒有幹事情,這個時候第二個入口便出現了
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 = 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:尼瑪這個框架還真是有點難,小釵感受有點小吃力啊,估計要讀到下週才能真正理解一點的了........