原生JS+觀察者模式實現一個模塊加載器

githubjavascript

簡單的瀏覽器端js模塊加載器[代碼解讀],這篇文章中,瞭解了一個簡單的require是如何實現的。java

最近看了如何實現一個MVVM的文章,恍然大悟,其實模塊加載器也可使用相似的方法。這裏,每個callback就被放在一個實例化的Watcher對象裏。git

參考Vue.js的數據雙向綁定實現方式,將每個模塊放入一個訂閱器Dep中,將每個task(依賴於該模塊的回調函數)放入一個Watcher中。同一個Dep有多個依賴它的Watcher。len是每個Wathcer所依賴模塊的數目。當模塊裝載好後,notify它的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,這個Watcher的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

相關文章
相關標籤/搜索