一步一步帶你分析 requirejs

詳細源代碼一共就2000多行,來看我這篇分析的同窗應該都下載下來了,好了,話很少說,開始:javascript

 

代碼的開頭就出現3個全局變量: requirejs, require, definehtml

var requirejs, require, define;

(function(global, setTimeout){
 
balababla......

})(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)))

  

require 和 define 你們應該都知道上幹什麼的,說實話,我是不知道的,在分析代碼的時候,我歷來也沒用過這個框架,就聽過AMD,就來直接看源碼了。java

若是你也不是很清楚,這2個變量是幹什麼的,我就來簡單介紹一下,懂得的同窗要是發現我說錯了,但願指點我也一下。windows

 主頁面 index.html:瀏覽器

  注意src是引入咱們的requirejs庫,  data-main:就是咱們第一次用requrie的地方:閉包

 

<html>
<head>
    <title></title>
</head>
<body>
  <script src="require.js" type="text/javascript" data-main="main.js"></script>
</body>
</html>

 main.js:app

  這裏2個代碼塊都是依賴require的:框架

(1)require.config:配置函數

(2)requrie(); 加載須要的函數,注意裏面的 ['name', 'say'],其實都是文件名,它們都在./js/ 目錄下,具體看conifgrequirejs

 

require.config({
  baseUrl: '',
  paths: {
    'nameDep': 'js/nameDep',
    'say': 'js/say',
    'name': 'js/name'
  },
  shim: {
    'name': {
      deps: ['nameDep']
    }
  }
});
require(['name', 'say'], function (name, say) {
  say(name);
});

  

./js/name.js  和 ./js/say.js

//name
define([''], function () {
  return '測試';
});

  

//say
define([], function () {
  return function (name) {
    console.log(name);
  };
});

  

最後注意在config中有個skim,這裏面也是定義js文件的,只是因爲他可能不符合AMD加載的規範

 ./js/nameDep.js

 

console.log("nameDep.js")

  

以上分析,咱們大概看得出  require 和 define 都是 function 

--------------------------------------分割線-------------------------------------------------------------------------

下面進入2000多行匿名函數的講解:

首先說一下2個參數: global 和 setTimeout 

(function (global, setTimeout) {
 

})(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout))

  

若是在瀏覽器的環境中,就是平時咱們所知的  windows對象 和 windows.setTimeout方法 。本文只討論瀏覽器環境,別的環境咱不考慮。

 

接下來就是一大堆的變量和函數的定義,太多了!反正我就掃了一眼,記不住?無所謂,只須要記住這開頭這3個變量就好了:req,contexts,cfg

 

2個是空對象  cfg,contexts ,還有一個函數 req 。

 

爲何它是函數??? 你一拉到底,必定會看到 req = requirejs = function(){} 的定義,眼神很差也不要緊,看我下面的代碼框裏有req({});

 

  var req, 
       ....
       contexts = {},
        cfg = {},
       ....
      //各類function定義
      ....

        //Create default context.
      req({});
      
     .....

  

好了把眼神定位到 req({})吧, 由於這是上面一大堆定義後,第一次執行了代碼!!!!

 

req這個函數長什麼樣子?? 你用文字匹配往上找:

 

/**
     * Main entry point.
     *
     * If the only argument to require is a string, then the module that
     * is represented by that string is fetched for the appropriate context.
     *
     * If the first argument is an array, then it will be treated as an array
     * of dependency string names to fetch. An optional function callback can
     * be specified to execute when all of those dependencies are available.
     *
     * Make a local req variable to help Caja compliance (it assumes things
     * on a require that are not standardized), and to give a short
     * name for minification/local scope use.
     */
    req = requirejs = function (deps, callback, errback, optional) {

        //Find the right context, use default
        var context, config,
            contextName = defContextName; //開頭定義了 defContextName = "_"

        // Determine if have config object in the call.
        if (!isArray(deps) && typeof deps !== 'string') {
            // deps is a config object
            config = deps;
            if (isArray(callback)) {
                // Adjust args if there are dependencies
                deps = callback;
                callback = errback;
                errback = optional;
            } else {
                deps = [];
            }
        }


           //第一次沒有config.context 跳過
        if (config && config.context) {
            contextName = config.context;
        }

           //第一次context == undefined
        context = getOwn(contexts, contextName);

           //第一次進入
        if (!context) {

            //只有第一次會調用newContext("_")
            context = contexts[contextName] = req.s.newContext(contextName);

        }
        
        if (config) {
            context.configure(config);
        }

        return context.require(deps, callback, errback);
    };

  

上面代碼  context出現的頻率很是高,說明這小子很重要。那咱們來看看這小子究竟是啥!!!

 

直接找到它的定義: 

 context = contexts[contextName] = req.s.newContext(contextName);

  

  那 newContext 又是什麼鬼! 搜它!!!!!

  

function newContext(contextName) {
 
       var context
          ...
       //一大堆function定義
         ....
        context={
              .....          

         }
        context.require = context.makeRequire();
        return context;
    }

  

用20秒的時間掃一眼!老套路,一大堆的變量,函數的定義。 一直到最後return 一個 context變量。

再往上拉看看 context 究竟是個什麼鬼!!

記憶力沒毛病的話,發現context對象裏裝的全是剛纔定義過一些變量,函數。

-----------------------------------------------------------------------------------------------

|    其中return的上一行代碼:                                                                   |

|              context.require = context.makeRequire();                                  |

|      有興趣就去看一眼,很簡單,就是返回一個叫localRequire的閉包     |

------------------------------------------------------------------------------------------------

OK,這個newContext函數咱們已經差很少了解了,就是返回一個對象,經過這個對象控制newContext裏的一系列私有變量和對象。

咱們的contexts就以下圖所示:

 

 

接下去走 

      context.configure(config);

我建議你去看一下,由於裏面啥也沒幹,你只須要花10秒左右的時間就能夠看完。。。

 

而後走

 return context.require(deps, callback, errback);

看上面的圖,context.require 就是 localRequire

 

接下來,咱們走進localRequire的世界裏一探究竟!!!!其中deps = [ ]

  function localRequire(deps, callback, errback) {
                    var id, map, requireMod;

                    if (options.enableBuildCallback && callback && isFunction(callback)) {
                        callback.__requireJsBuild = true;
                    }

                    if (typeof deps === 'string') {
                        if (isFunction(callback)) {
                            //Invalid call
                            return onError(makeError('requireargs', 'Invalid require call'), errback);
                        }

                        //If require|exports|module are requested, get the
                        //value for them from the special handlers. Caveat:
                        //this only works while module is being defined.
                        if (relMap && hasProp(handlers, deps)) {
                            return handlers[deps](registry[relMap.id]);
                        }

                        //Synchronous access to one module. If require.get is
                        //available (as in the Node adapter), prefer that.
                        if (req.get) {
                            return req.get(context, deps, relMap, localRequire);
                        }

                        //Normalize module name, if it contains . or ..
                        map = makeModuleMap(deps, relMap, false, true);
                        id = map.id;

                        if (!hasProp(defined, id)) {
                            return onError(makeError('notloaded', 'Module name "' +
                                        id +
                                        '" has not been loaded yet for context: ' +
                                        contextName +
                                        (relMap ? '' : '. Use require([])')));
                        }
                        return defined[id];
                    }

                    //Grab defines waiting in the global queue.
                    intakeDefines();

                    //Mark all the dependencies as needing to be loaded.
                    context.nextTick(function () {
                        //Some defines could have been added since the
                        //require call, collect them.
                        intakeDefines();

                        requireMod = getModule(makeModuleMap(null, relMap));

                        //Store if map config should be applied to this require
                        //call for dependencies.
                        requireMod.skipMap = options.skipMap;

                        requireMod.init(deps, callback, errback, {
                            enabled: true
                        });

                        checkLoaded();
                    });

                    return localRequire;
                }

  

很快咱們就把目光鎖定到(由於,前面的一大段代碼都是if語句,不符合條件進不去)

      //Grab defines waiting in the global queue.
                    intakeDefines();

  

intakeDefines --》takeGlobalQueue--》context.nextTick--》intakeDefines --》  requireMod.init --》  checkLoaded();

 

裏面大體就是上面所示,由於有點複雜,我這裏暫時先不說,接續走簡單的。。

 

到此爲止,咱們已經走完了一邊req。

大體流程以下圖所示:

 

 

接下去,咱們再一拉到底!!!!

倒數第二行:

 

    //Set up with config info.
    req(cfg);

 

cfg還記得是什麼嗎?? 一開始是定義爲空對象啊!!!

 

有地方修改過它嗎? 往上拉一拉!!

  //Look for a data-main script attribute, which could also adjust the baseUrl.
  if (isBrowser && !cfg.skipDataMain) {
    //Figure out baseUrl. Get it from the script tag with require.js in it.
    eachReverse(scripts(), function(script) {
      //Set the 'head' where we can append children by
      //using the script's parent.
      if (!head) {
        head = script.parentNode;
      }

      //Look for a data-main attribute to set main script for the page
      //to load. If it is there, the path to data main becomes the
      //baseUrl, if it is not already set.
      dataMain = script.getAttribute('data-main');
      if (dataMain) {
        //Preserve dataMain in case it is a path (i.e. contains '?')
        mainScript = dataMain;
 
        //Set final baseUrl if there is not already an explicit one,
        //but only do so if the data-main value is not a loader plugin
        //module ID.
        if (!cfg.baseUrl && mainScript.indexOf('!') === -1) {

          //Pull off the directory of data-main for use as the
          //baseUrl.
          src = mainScript.split('/');

          mainScript = src.pop();

          subPath = src.length ? src.join('/') + '/' : './';

          cfg.baseUrl = subPath;
        }

        //Strip off any trailing .js since mainScript is now
        //like a module name.
        mainScript = mainScript.replace(jsSuffixRegExp, '');

        //If mainScript is still a path, fall back to dataMain
        if (req.jsExtRegExp.test(mainScript)) {

          mainScript = dataMain;
        }

        //Put the data-main script in the files to load.
        cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];

        return true;
      }
    });
  }

  

這裏面作的事情很簡單,就是找到頁面中包含 "data-main" 屬性的script標籤,分析路徑。

最後獲得這樣的結果:

 

 

好了。再回到req(cfg);

這一次咱們好好的走一走上面流程圖裏的步驟。。。。

 

把目光迅速鎖定到:

  return context.require(deps, callback, errback);

  

 

相關文章
相關標籤/搜索