githubjavascript
在 簡單的瀏覽器端js模塊加載器[代碼解讀],這篇文章中,瞭解了一個簡單的require是如何實現的。java
最近看了如何實現一個MVVM的文章,恍然大悟,其實模塊加載器也可使用相似的方法。這裏,每個callback就被放在一個實例化的Watcher對象裏。git
參考Vue.js的數據雙向綁定實現方式,將每個模塊放入一個訂閱器Dep中,將每個task(依賴於該模塊的回調函數)放入一個Watcher中。同一個Dep有多個依賴它的Watcher。len減一。當$len爲零,執行這個Watcher的taskgithub
function createWatcher(callback, deps, dep) {
return new Watcher(callback, deps, dep, Module)
}
複製代碼
咱們最後暴露的requirejs會指向下面這個對象。baseUrl被設置後,全部的路徑都相對於該baseUrl,若是沒有被設置,那麼咱們會將當前require.js的地址做爲baseUrl,module存放模塊。數組
let Module = {
config: {
baseUrl: "",
paths: {}
},
module: {
},
host: location.protocol + '//' + location.host
};
複製代碼
callback是咱們須要執行的函數。這個callback能夠是require裏面的待執行函數,也能夠是define裏面有依賴另外一個define的函數。若是define裏面沒有依賴,則不會放入Wathcer裏面。瀏覽器
require(['./ww.js', function(ww){
//...
}]);
define(['./aa.js', function(aa){
return aa
}]);
複製代碼
咱們再來看看Watcher這個構造函數。task是一個待執行的callback,uris是這個異步callback所依賴的模塊(地址),dep是一個訂閱器。len變量就減一。對於一個Watcher,咱們不用關心當前究竟是哪一個模塊加載好了,反正只能是全部依賴模塊加載好,這個task才能被執行。因此當$len爲零的時候,表面依賴所有加載好,那麼這個Wathcer就執行這個taskapp
function Watcher(task, uris, dep, Module){
this.$task = task;
this.$uris = uris;
this.dep = dep;
this.$Module = Module;
this.modArr = [];
this.$len = this.$uris.length;
}
複製代碼
Watcher每執行一次update,this.$len--。當爲零的時候,執行this.run()方法。this.run()中,若是task是一個函數,那麼執行執行。由於在define函數中,若是define裏面沒有依賴,就會將其callback直接放入Watcher。若是有依賴,則會先建立一個task對象,將當前define腳本的src存入task,以便觸發該dep的notify方法。異步
Watcher.prototype = {
update: function () {
this.$len--;
if (this.$len <= 0) {
this.run();
}
},
run: function () {
let mod = this.$Module.module,
task = this.$task;
this.$uris.forEach(uri => {
this.modArr.push(mod[uri].obj);
});
//this.$Module.module[this.dep.depName].obj =
if (typeof task == 'function') {
task.apply(null, this.modArr);
return
}
let src = task.currentSrc;
mod[src].obj = task.callback.apply(null, this.modArr);
mod[src].dep.notify();
this.dep.removeSub(this);
return
}
};
複製代碼
下面咱們來說講Dep訂閱器。對於每個模塊,咱們用一個訂閱器來存放它,它的subs數組,存放全部依賴於它才能執行的task,即Watcher。無論define有多深,模塊a依賴於模塊b,模塊b依賴於模塊c。當模塊c加載好後(約定模塊c是不依賴於任何其餘模塊的),模塊c的訂閱器dep觸發notify方法,subs裏面的Watcher的update方法。函數
function Dep(depName){
this.id = uid++;
this.subs = [];
this.depName = depName;
}
Dep.prototype = {
/** * 添加訂閱, 將watcher添加進數組subs * @param {Object} task new watcher() */
addSubs: function(task){
this.subs.push(task);
},
/** * 刪除訂閱, 將watcher從數組subs中刪除 * @param {Object} task new watcher() */
removeSub: function(task){
let index = this.subs.indexOf(task);
(index != -1) && this.subs.splice(index, 1);
},
/** * 當該模塊加載好的時候, 通知全部依賴它的task */
notify: function(){
this.subs.forEach(task => {
task.update();
});
}
};
複製代碼
以上是代碼的部分解析...requirejs