原生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

相关文章
相关标签/搜索