【requireJS源碼學習01】瞭解整個requireJS的結構

前言

如今工做中基本離不開requireJS這種模塊管理工具了,以前一直在用,可是對其原理不甚熟悉,整兩天咱們來試着學習其源碼,然後在探尋其背後的AMD思想吧javascript

因而今天的目標是熟悉requireJS總體框架結構,順便看看以前的簡單demohtml

程序入口

源碼閱讀仍然有必定門檻,通看的作法不適合我等素質的選手,因此仍是得由入口開始,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>]

這個地方遇到兩個方法

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;
      }
    }
  }
}
View Code

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;
  }
}
View Code
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);
  }
},
View Code

首次傳入的是空對象,因此開始一段代碼暫時沒有意義,這裏使用的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;
      }
    }
  }
}
View Code

這個所謂的入口執行後實際的意義基本等於什麼都沒有幹......

可是,這裏能夠得出一個弱弱的結論就是

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;
}
View Code

過程當中會執行一次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
  };
}
View Code

而後是咱們關鍵的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];
    }
  }
};
View Code

總的來講,這個模塊仍是很長的,首先是其構造函數

這裏仍有不少東西讀不懂,因此就所有過吧,反正今天的主要目的是熟悉總體框架

這裏實例化結束後便造成了一個模塊暫存於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();
},
View Code

而後又會調用 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;
    }

  }
},
View Code

而後今天累了,明天繼續吧......

結語

今天的目標是熟悉requireJS的總體結構,若是沒有錯覺或者解讀失誤,咱們應該大概瞭解了requireJS的總體結構,因而讓咱們明天繼續吧

PS:尼瑪這個框架還真是有點難,小釵感受有點小吃力啊,估計要讀到下週才能真正理解一點的了........

相關文章
相關標籤/搜索