【requireJS源碼學習03】細究requireJS的加載流程

前言

這個星期折騰了一週,中間沒有什麼時間學習,週末又幹了些其它事情,這個時候正好有時間,咱們一塊兒來繼續學習requireJS吧前端

仍是那句話,小釵以爲requireJS自己仍是有點難度的,估計徹底吸取這個月就過去了,等requireJS學習結束後,咱們的學習流程可能就朝兩個方向走node

① 單頁應用框架/UI庫整理數組

② UML文檔相關/重構思想相關(軟性素質)瀏覽器

而後以上的估計估計會持續三、4個月時間,但願學習下來本身能有不同的提升,成爲一個合格的前端,因而咱們繼續今天的內容吧緩存

requireJS中的隊列

通過以前的學習,咱們隊requireJS的大概結構以及工做有了必定認識,可是,咱們對於其中一些細節點事實上仍是不太清晰的,好比裏面的隊列相關閉包

requireJS中有幾種隊列,每種隊列是幹神馬的,這些是咱們須要挖掘的,並且也是真正理解requireJS實現原理的難點app

首先,requireJS有兩個隊列:框架

① globalDefQueue / 全局異步

② defQueue / newContext 閉包ide

這個隊列事實上是一個數組,他們具體幹了什麼咱們還不得而知,可是我下意識以爲他比較關鍵......

咱們這裏來簡單的理一理這兩個隊列

globalDefQueue 

這個是全局性的隊列,與之相關的第一個函數爲takeGlobalQueue

takeGlobalQueue

/**
* Internal method to transfer globalQueue items to this context's
* defQueue.
*/
function takeGlobalQueue() {
  //Push all the globalDefQueue items into the context's defQueue
  if (globalDefQueue.length) {
    //Array splice in the values since the context code has a
    //local var ref to defQueue, so cannot just reassign the one
    //on context.
    apsp.apply(defQueue, [defQueue.length - 1, 0].concat(globalDefQueue));
    globalDefQueue = [];
  }
}

這個函數中涉及到了defQueue中的的操做,每一次有效操做後都會將全局隊列清空,其中有一個apsp方法這個是數組的splice方法

該函數主要用於將globalDefQueue中的數據導入defQueue,而globalDefQueue只會有可能在define函數出被壓入數據,具體緣由還得日後看

因此這裏的takeGlobalQueue其實就如註釋所說,將全局隊列中的項目轉入context defQueue中

define

第二個涉及globalDefQueue函數爲define

/**
* The function that handles definitions of modules. Differs from
* require() in that a string for the module should be the first argument,
* and the function to execute after dependencies are loaded should
* return a value to define the module corresponding to the first argument's
* name.
*/
define = function (name, deps, callback) {
  var node, context;

  //Allow for anonymous modules
  if (typeof name !== 'string') {
    //Adjust args appropriately
    callback = deps;
    deps = name;
    name = null;
  }

  //This module may not have dependencies
  if (!isArray(deps)) {
    callback = deps;
    deps = null;
  }

  //If no name, and callback is a function, then figure out if it a
  //CommonJS thing with dependencies.
  if (!deps && isFunction(callback)) {
    deps = [];
    //Remove comments from the callback string,
    //look for require calls, and pull them into the dependencies,
    //but only if there are function args.
    if (callback.length) {
      callback
                  .toString()
                  .replace(commentRegExp, '')
                  .replace(cjsRequireRegExp, function (match, dep) {
                    deps.push(dep);
                  });

      //May be a CommonJS thing even without require calls, but still
      //could use exports, and module. Avoid doing exports and module
      //work though if it just needs require.
      //REQUIRES the function to expect the CommonJS variables in the
      //order listed below.
      deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
    }
  }

  //If in IE 6-8 and hit an anonymous define() call, do the interactive
  //work.
  if (useInteractive) {
    node = currentlyAddingScript || getInteractiveScript();
    if (node) {
      if (!name) {
        name = node.getAttribute('data-requiremodule');
      }
      context = contexts[node.getAttribute('data-requirecontext')];
    }
  }

  //Always save off evaluating the def call until the script onload handler.
  //This allows multiple modules to be in a file without prematurely
  //tracing dependencies, and allows for anonymous module support,
  //where the module name is not known until the script onload event
  //occurs. If no context, use the global queue, and get it processed
  //in the onscript load callback.
  (context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
};
View Code

他會根據context是否初始化決定當前鍵值標識存於哪一個隊列,據代碼看來,若是是標準瀏覽器應該都會先走globalDefQueue隊列

而後就沒有而後了,咱們接下來再看看吧

defQueue 

首先defQueue處於newContext閉包環境中,按照以前的知識來看,newContext每次也只會執行一次,因此這個defQueue之後會被各個函數共享

操做defQueue的第一個函數爲

intakeDefines

function intakeDefines() {
  var args;
  //Any defined modules in the global queue, intake them now.
  takeGlobalQueue();
  //Make sure any remaining defQueue items get properly processed.
  while (defQueue.length) {
    args = defQueue.shift();
    if (args[0] === null) {
      return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
    } else {
      //args are id, deps, factory. Should be normalized by the
      //define() function.
      callGetModule(args);
    }
  }
}

引入定義,第一件事情就是將globalDefQueue中的項目移入defQueue中,然後將其中的項目一個個取出並執行callGetModule方法,可是我這裏好像都沒有效果,這塊先忽略之

第二個函數爲completeLoad

completeLoad

/**
* Internal method used by environment adapters to complete a load event.
* A load event could be a script load or just a load pass from a synchronous
* load call.
* @param {String} moduleName the name of the module to potentially complete.
*/
completeLoad: function (moduleName) {
  var found, args, mod,
              shim = getOwn(config.shim, moduleName) || {},
              shExports = shim.exports;

  takeGlobalQueue();

  while (defQueue.length) {
    args = defQueue.shift();
    if (args[0] === null) {
      args[0] = moduleName;
      //If already found an anonymous module and bound it
      //to this name, then this is some other anon module
      //waiting for its completeLoad to fire.
      if (found) {
        break;
      }
      found = true;
    } else if (args[0] === moduleName) {
      //Found matching define call for this script!
      found = true;
    }

    callGetModule(args);
  }

  //Do this after the cycle of callGetModule in case the result
  //of those calls/init calls changes the registry.
  mod = getOwn(registry, moduleName);

  if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
    if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
      if (hasPathFallback(moduleName)) {
        return;
      } else {
        return onError(makeError('nodefine',
                                        'No define call for ' + moduleName,
                                        null,
                                        [moduleName]));
      }
    } else {
      //A script that does not call define(), so just simulate
      //the call for it.
      callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
    }
  }

  checkLoaded();
},
View Code

這個會將globalDefQueue中的隊列項搞到defQueue中,而後處理一下就調用callgetModule方法,其中參數是這樣的

 

callGetModule

function callGetModule(args) {
  //Skip modules already defined.
  if (!hasProp(defined, args[0])) {
    getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
  }
}

這個時候就會由全局registry中獲取當前的模塊了,而後執行他的init方法,這裏會加載script標籤,將其依賴項載入,這裏還會涉及到registry的操做,咱們放到後面來學習

而completeLoad是在script標籤加載結束後調用的方法

/**
* callback for script loads, used to check status of loading.
*
* @param {Event} evt the event from the browser for the script
* that was loaded.
*/
onScriptLoad: function (evt) {
  //Using currentTarget instead of target for Firefox 2.0's sake. Not
  //all old browsers will be supported, but this one was easy enough
  //to support and still makes sense.
  if (evt.type === 'load' ||
            (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
    //Reset interactive script so a script node is not held onto for
    //to long.
    interactiveScript = null;

    //Pull out the name of the module and the context.
    var data = getScriptData(evt);
    context.completeLoad(data.id);
  }
},

因此咱們這裏來從新整理下requireJS的執行流程(可能有誤)

① 引入requireJS標籤後,首先執行一些初始化操做

② 執行req({})初始化newContext,而且保存至contexts對象中

③ 執行req(cfg),將讀取的data-main屬性而且封裝爲參數實例化模塊

④ 執行main.js中的邏輯,執行require時候,會一次加載name與say

⑤ 調用依賴時候會根據define進行設置將加載好的標籤引入鍵值對應關係,執行點是load事件

因此關鍵點再次回到了main.js加載以後作的事情

require方法

通過以前的學習,面對requireJS咱們大概知道了如下事情

① require是先將依賴項加載結束,而後再執行後面的函數回調

首先第一個就是一個難點,由於require如今是採用script標籤的方式引入各個模塊,因此咱們不能肯定什麼時候加載結束,因此這裏存在一個複雜的判斷以及緩存

② 依賴列表以映射的方式保存對應的模塊,事實上返回的是一個執行後的代碼,返回多是對象多是函數,可能什麼也沒有(不標準)

這個也是一塊比較煩的地方,意味着,每個define模塊都會維護一個閉包,並且多數時候這個閉包是沒法釋放的,因此真正大模塊的單頁應用有可能越用越卡

面對這一問題,通常採用將大項目分頻道的方式,以避免首次加載過多的資源,防止內存佔用過分問題

③ 加載模塊時候會建立script標籤,這裏爲其綁定了onload事件判斷是否加載結束,如果加載結束,會在原來的緩存模塊中找到對應模塊而且爲其賦值,這裏又是一個複雜的過程

require的總體理解之因此難,我以爲就是難在異步加載與循環依賴一塊,異步加載致使程序比較晦澀

因此咱們再次進入程序看看,這一切是如何發生的,這裏先以main.js爲例

再說main模塊的加載

通過以前的學習,main模塊加載以前會經歷以下步驟

① require調用req({})初始化一個上下文環境(newContext)

② 解析頁面script標籤,碰到具備data-main屬性的標籤便停下,而且解析他造成第一個配置項調用req(cfg)

③ 內部調用統一入口requirejs,並取出上文實例化後的上下文環境(context),執行其require方法

④ 內部調用localRequire(makeRequire)方法,這裏幹了比較重要的事情實例化模塊

⑤ 模塊的實例化發生在localRequire中,這裏的步驟比較關鍵

首先,這裏會調用nextTick實際去建立加載各個模塊的操做,可是這裏有一個settimeout就比較麻煩了,全部的操做會拋出主幹流程以外

這樣作的意義我暫時不能瞭解,可能這段邏輯會異步加載script標籤,如果不拋到主幹流程外會有問題吧,如果您知道請告知

nextTick使用 settimeout 的緣由不明,待解決/經測試不加延時可能致使加載順序錯亂

咱們這裏幹一件不合理的事情,將nexttick的延時給去掉試試整個邏輯

req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
  setTimeout(fn, 4);
} : function (fn) { fn(); };

req.nextTick = function (fn){ fn();};

如此,除了script加載一塊就再也不具備異步的問題了,這裏咱們來重新理一理

深刻req(cfg)

第一步調用req(cfg)

第二步處理參數而且調用require

能夠看到,通過require.config(config)的處理,相關的參數已經放到了實例裏面

第三步調用localRequire,而且進入nextTick流程,一個要注意的地方是這裏的this指向的是context

第四步執行intakeDefines,將全局的依賴裝入,這裏是沒有的

第五步實例化模塊(makeModuleMap),創建映射關係,最後會返回相似這樣的東西

第六步便將此映射關係傳入getModule創建相關模塊,而後傳入該映射關係對象創建模塊,Module類根據參數對象做簡單初始化便返回

第七步調用mod的init方法真正操做該模塊,這裏會執行加載邏輯Module的init方法,最後會到context的load方法加載script標籤,值得注意的是加載結束後這裏會綁定onScriptLoad方法

第八步加載成功後會調用context.completeLoad(data.id)方法

由於以前定義過該模塊了,這裏只是將其取出(mod = getOwn(registry, moduleName))而後再調用其模塊init方法又會走一連串邏輯,最後再check一塊結束

if (this.map.isDefine && !this.ignore) {
  defined[id] = exports;

  if (req.onResourceLoad) {
    req.onResourceLoad(context, this.map, this.depMaps);
  }
}

由於每個加載模塊都會定義一個事件,在其實際加載結束後會執行之

if (this.defined && !this.defineEmitted) {
  this.defineEmitted = true;
  this.emit('defined', this.exports);
  this.defineEmitComplete = true;
}

最後會調用checkLoaded檢查是否還有未加載的模塊,總之這步結束後基本上就main.js就加載結束了,這個由全局contexts中的defined對象能夠看出

這裏仍然有一塊比較難,由於在main.js加載結束前還未執行其load事件,其下一步的require流程又開始了

contexts._.defined
Object {}

這個時候全局的defined尚未東西呢,因此他這裏會有一個狀態機作判斷,不然最後不會只是main.js中的fn

require(['name', 'say'], function (name, say) {
  say(name);
});

他們判斷的方式就是不停的check,不停的check,直到加載成功結束

main.js中的require

由於main模塊並不具備模塊,因此其執行邏輯仍是稍有不一樣的,咱們如今將關注點放到main.js中的require相關邏輯

require(['name', 'say'], function (name, say) {
  say(name);
});

首次進入這個邏輯時候事實上main.js的onload事件並未執行,因此全局contexts._.defined對象依舊爲空,這裏進入了實際模塊的加載邏輯既有依賴項又有回調

PS:這裏有一個比較有意思的作法就是將原來的nextTick的settimeout幹掉這裏的狀況會有所不一樣

依舊進入context.require流程

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

期間會碰到main.js onload事件觸發,並致使

contexts._.defined => Object {main: undefined}

第二步即是這裏的會建立一個模塊,這個與,然後調用其init方法,這裏須要注意的是傳入了deps(name, say)依賴,因此這裏的depMaps便不爲空了

而且這裏將當前回調傳給factory,而且將依賴的name與say模塊保存

 

this.factory = factory;
this.depMaps = depMaps && depMaps.slice(0);

進入enable流程,首先註冊當前對象之閉包(newContext)enableRegistry中

這裏有一個操做是若是具備依賴關係,咱們這裏便依賴於say以及name會執行一個邏輯

//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);
  }
}));

循環的載入其依賴項,並造成模塊,這裏都會搞進enableRegistry中,好比這段邏輯結束先後有所不一樣

事實上對應模塊初始化已經結束,進入了script待加載邏輯,只不過暫時卡到這裏了......

而後這裏會進入其check邏輯,因爲這裏defineDep等於2因此不會執行函數回調,而直接跳出,這裏有一個關鍵即是咱們的Registry未被清理

以上邏輯只是在main.js中require方法執行後所執行的邏輯,確切的說是這段代碼所執行的邏輯

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

而後會執行一個checkLoaded方法檢測enabledRegistry中未加載完成的模塊而且進行清理,這段邏輯比較關鍵

function checkLoaded() {
  var map, modId, err, usingPathFallback,
            waitInterval = config.waitSeconds * 1000,
  //It is possible to disable the wait interval by using waitSeconds of 0.
            expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
            noLoads = [],
            reqCalls = [],
            stillLoading = false,
            needCycleCheck = true;

  //Do not bother if this call was a result of a cycle break.
  if (inCheckLoaded) {
    return;
  }

  inCheckLoaded = true;

  //Figure out the state of all the modules.
  eachProp(enabledRegistry, function (mod) {
    map = mod.map;
    modId = map.id;

    //Skip things that are not enabled or in error state.
    if (!mod.enabled) {
      return;
    }

    if (!map.isDefine) {
      reqCalls.push(mod);
    }

    if (!mod.error) {
      //If the module should be executed, and it has not
      //been inited and time is up, remember it.
      if (!mod.inited && expired) {
        if (hasPathFallback(modId)) {
          usingPathFallback = true;
          stillLoading = true;
        } else {
          noLoads.push(modId);
          removeScript(modId);
        }
      } else if (!mod.inited && mod.fetched && map.isDefine) {
        stillLoading = true;
        if (!map.prefix) {
          //No reason to keep looking for unfinished
          //loading. If the only stillLoading is a
          //plugin resource though, keep going,
          //because it may be that a plugin resource
          //is waiting on a non-plugin cycle.
          return (needCycleCheck = false);
        }
      }
    }
  });

  if (expired && noLoads.length) {
    //If wait time expired, throw error of unloaded modules.
    err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
    err.contextName = context.contextName;
    return onError(err);
  }

  //Not expired, check for a cycle.
  if (needCycleCheck) {
    each(reqCalls, function (mod) {
      breakCycle(mod, {}, {});
    });
  }

  //If still waiting on loads, and the waiting load is something
  //other than a plugin resource, or there are still outstanding
  //scripts, then just try back later.
  if ((!expired || usingPathFallback) && stillLoading) {
    //Something is still waiting to load. Wait for it, but only
    //if a timeout is not already in effect.
    if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
      checkLoadedTimeoutId = setTimeout(function () {
        checkLoadedTimeoutId = 0;
        checkLoaded();
      }, 50);
    }
  }

  inCheckLoaded = false;
}

他首先會遍歷enableRegistry取出其中定義的模塊,而且將沒有加載成功的模塊標識注入noLoads數組,若是過時了這裏就會報錯

若是上述沒問題還會作循環依賴的判斷,主要邏輯在breakCycle中,由於咱們這裏不存在循環依賴便跳出了,但還未結束

咱們這裏開始了遞歸檢測依賴是否載入

if ((!expired || usingPathFallback) && stillLoading) {
  //Something is still waiting to load. Wait for it, but only
  //if a timeout is not already in effect.
  if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
    checkLoadedTimeoutId = setTimeout(function () {
      checkLoadedTimeoutId = 0;
      checkLoaded();
    }, 50);
  }
}

若是模塊沒有載入,這裏就會一直繼續,直到全部模塊加載結束,其判斷點又在各個define方法中,define方法會根據鍵值改變對應模塊的標識值

幾個關鍵判斷點爲:

① checkLoadedTimeoutId

② inCheckLoaded

③ stillLoading

可是最終的判斷點事實上來源與mod的mod.inited/fetched/isDefine等屬性,因此咱們這裏須要來理一理

首次模塊執行init方法時會執行

 this.inited = true;

由於初始化時候動態的傳入了enabled爲true因此首次會執行enable邏輯

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

if (options.enabled || this.enabled) {
  //Enable this module and dependencies.
  //Will call this.check()
  this.enable();
} else {
  this.check();
}

因而達成enabled爲true的條件,這裏而且會爲該模塊的依賴執行enable操做,而且爲其支持defined事件在加載結束後會觸發之

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);
  }
}));

這裏的邏輯比較關鍵,然後才執行該模塊的check方法

PS:讀到這裏,才大概對requireJS的邏輯有必定認識了

跳入check方法後便會將defining設置爲true由於這裏的依賴項未載入結束,因此這裏的depCount爲2,因此不會觸發

this.defined = true;

因此下面的遞歸settimeout會一直執行,直到成功或者超時,這裏咱們進入define相關流程

define方法

這裏以say爲例,在加載文件結束時候會觸發其define方法,這裏主要向globalDefQueue中插入當前模塊的隊列,而這裏上面作過介紹

而這裏的關鍵會在script標籤執行onload事件時候將全局隊列的東西載入context.defQueue

而這個時候又會根據相關的映射id(由event參數獲取),實例化相關模塊(事實上是取得當前模塊,以前已經實例化),這個時候又會進入check邏輯,這個時候是say模塊的check邏輯

say無依賴,而且加載結束,這裏會將當前模塊與其返回值作依賴,這裏是一個函數,這裏factory與exports的關係是

而後會將當前鍵值右Registry相關刪除,完了便會進入下面的邏輯,值得注意的是這裏會觸發前面爲say模塊註冊的defined事件

PS:這裏必定要注意,這裏的say模塊裏面定義的傢伙被執行了!!!

//註冊點
on(depMap, 'defined', bind(this, function (depExports) {
  this.defineDep(i, depExports);
  this.check();
}));

//觸發點
this.emit('defined', this.exports);

//關鍵點,用於消除依賴
this.defineDep(i, depExports);

因此,咱們的總體邏輯基本出來了

結語

最後,咱們來一次總結,對初次的requireJS學習畫下一個初步的句點

① requireJS會初始化一個默認上下文出來

req({}) => newContext

② 加載main.js,main.js與基本模塊不太一致,加載結束便會執行裏面邏輯對主幹流程沒有太大影響

③ 執行main.js中的require.config配置,最後調用require方法

④ 調用時候會將數組中的依賴項載入,而且實例化一個匿名模塊出來(mod)

由於主幹(匿名)模塊依賴於say與name,因此會在enable中實例化兩個模塊而且將當前實例depCount設置爲2

⑤ 各個依賴模塊也會執行加載操做,say以及name,如果有依賴關係會循環執行enable

⑥ 會執行主幹模塊的check操做因爲depCount爲2便執行其它邏輯,這裏爲其註冊了defined事件

⑦ 執行checkLoaded方法,這裏會開始遞歸的檢查模塊是否加載結束,必定要在主幹模塊depCount爲0 時候纔會執行其回調,而且會傳入say與name返回值作參數

⑧ 當模塊加載結束後會觸發其onScriptLoad => completeLoad事件

⑨ 由於各個define模塊會想全局隊列壓入標識的值,而且會根據他獲取相關模塊而且執行其init事件

10 這個時候會執行模塊的實例化init方法,而且會檢測該模塊的依賴,say沒有依賴便繼續向下,將其factory方法執行回指exports(具備參數,參數是依賴項)

PS:其依賴項是在解除依賴時候注入的defineDep

11 最後全部依賴模塊加載時候,最後主幹的depCount也就變成了0了,這個時候便會執行相似say的邏輯觸發回調

這裏的關鍵就是,加載主幹模塊時候會檢查器依賴項,而且爲每個依賴項註冊defined事件,其事件又會執行check方法

這也意味着,每個依賴模塊檢查成功事實上都有可能執行主幹流程的回調,其條件是主幹的depCount爲0,這塊就是整個requireJS的難點所在......

幾個關鍵即是

① require時候的模塊定義以及爲其註冊事件

② 文件加載結束define將該模塊壓入全局隊列

③ script加載成功後觸發全局隊列的檢查

④ 各個子模塊加載結束,而且接觸主模塊依賴執,而且將自我返回值賦予行主模塊實例數組depExports

⑤ 當主模塊depCount爲0 時候終於即可以觸發了,因而邏輯結束

最後,小釵渾渾噩噩的初步學習requireJS結束,感受有點小難,等後面技術有所提升後便再學習吧

相關文章
相關標籤/搜索