前兩天跟著葉小釵的博客,看了下RequireJS的源碼,大體瞭解了其中的執行過程。不過在何時進行依賴項的載入,以及具體的代碼在何處執行,還沒有搞透徹,奈何能力不夠,只能先記錄一下了。 RequireJS的初探 看源碼從頭開始看,肯定是不切實際的。按照葉小釵的方法,是從data main開始的,所以我 ...
前兩天跟著葉小釵的博客,看了下RequireJS的源碼,大體瞭解了其中的執行過程。不過在何時進行依賴項的載入,以及具體的代碼在何處執行,還沒有搞透徹,奈何能力不夠,只能先記錄一下了。
RequireJS的初探
看源碼從頭開始看,肯定是不切實際的。按照葉小釵的方法,是從data-main開始的,所以我們也從那裡開始把!
首先,頁面會有一段js標簽,會去載入requirejs:
<script data-main="test.js" src="lib/require.js"></script>
Requirejs中,代碼是一個自執行的方法:
var requirejs,require,define;
(function(global){
})(this);
源碼中,主要是定義了三個全局的變數——requirejs,require,define,下麵是一個自執行的方法。
那麼主要就是看看這個方法裡面都幹了什麼吧!
RequireJS主體方法
//定義環境變數
//定義各種方法
//檢查requirejs,require,define
//核心部分
function newContext(){}//定義核心部分方法
req = requirejs = function(){//定義req
//...
return context.require();
};
req.config = function(){};
req({});//創建預設的上下文
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};
//洋洋灑灑,載入代碼
req.load = function(){
node = req.createNode(config, moduleName, url);//創建節點
node.addEventListener('load', context.onScriptLoad, false);//添加load事件
if (baseElement) {//插入到head裡面
head.insertBefore(node, baseElement);
} else {
head.appendChild(node);
}
};
if (isBrowser && !cfg.skipDataMain) {
//載入main.js
}
define = function(){};
req.exec =function(){};
req(cfg);//執行配置文件
上面的代碼中,關鍵的方法定義其實只有兩個:
- 定義了newContext()方法,用於配置上下文環境,並且僅會執行一次!後續都是使用同一個context!
- 定義req,它是後續使用的方法!
然後在上面的代碼中,它做了下麵三件事:
- 1 執行req({}),傳入了空的對象,初始化context
- 2 if(isBrowser && xxxx)....,載入data-main所指向的js,讀取配置
- 3 執行req(cfg),執行剛剛讀取的配置,載入目標模塊...
基本上就是這個套路了!
newContext()
RequireJS最精彩的部分,就在這個方法裡面了!
function newContext(contextName){
function getModule(depMap) {
var id = depMap.id,
mod = getOwn(registry, id);
if (!mod) {
mod = registry[id] = new context.Module(depMap);
}
return mod;
}
function checkLoaded() {
}
context = {
//...
makeRequire: function (relMap, options) {
//核心
function localRequire(deps, callback, errback) {
//真正的核心
context.nextTick(function () {
intakeDefines();
requireMod = getModule(makeModuleMap(null, relMap));//主要看這裡吧
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
}
return localRequire;
},
load: function (id, url) {
req.load(context, id, url);
},
onScriptLoad : function() {
context.completeLoad();
},
completeLoad : function() {
takeGlobalQueue();
while(defQueue.length){
}
mod = getOwn(registry, moduleName);
checkLoaded();
}
//...
}
Module.prototype = {
init : function(depMaps, factory, errback, options){
if (options.enabled || this.enabled) {
this.enable();
} else {
this.check();
}
},
fetch : function(){
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();//是否包含首碼 text!xxx
}
},
load: function () {
var url = this.map.url;
//Regular dependency.
if (!urlFetched[url]) {
urlFetched[url] = true;
context.load(this.map.id, url);
}
},
check : function(){
this.fetch();
},
enable : function(){
this.check();
}
};
context.require = context.makeRequire();//其實是把localRequire賦值給context.require
return context;
};
這個newContext()裡面定義大量的載入模塊、校驗、檢查等工作。可以看到這個方法,主要是定義了一個context對象和Module方法。
然後執行這個方法後,會自動調用context對象的makeRequire()方法,這個makeRequire實際上調用的又是內部定義的localRequire()。LocalRequire則是處理載入任務的核心——比如依賴的檢查,模塊的載入等等。
執行點
req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
setTimeout(fn, 4);
} : function (fn) { fn(); };
所有的載入都會交由這個nextTick執行,暫時沒有搞清楚...
流程圖
收穫
- 1 原來RequireJS載入模塊的時候,是檢查data-main屬性,然後去載入目標js。
- 2 載入到目標模塊後,會按照它的依賴關係,進行載入,並且每個模塊僅會載入一次。
- 3 載入模塊的時候,會綁定一個load事件,當載入完會觸發事件,執行該js
- 4 腳本實際上是通過創建了頁面的script元素,然後添加到head裡面。