zoukankan      html  css  js  c++  java
  • js模块加载详解

    看着java中各种import加载,在回过头来看看javascript还在自己造轮子,写各种XX的模块加载框架,ECMASCRIPT6不知什么时候能够普及。不过DT归DT,该学的还是要学。

    一 同步加载模式(SMD)

    同步顾名思义就是按顺序依次加载执行,比如A模块要引用B模块中的某些函数完成事情,那么此时B模块必须是已经存在页面内存中的,A调用顺利完成执行下面的操作。例子就是A模块直接调用document对象,因为document早已存在浏览器内存中。

    同步模块代码实现也比较简单,解析模块路径,执行会掉函数,调用模块

    定义一个模块

    var manger = (function(){
        var F = F || {};
        F.define = function(str, fn){
            var parts = str.split('.'),
                old = parent = this,//old保存祖父模块,parent保存当前模块的父模块
                i = len = 0;
            for(len = parts.length; i < len; i++){
                if(typeof parent[parts[i]] === 'undefined'){
                    parent[parts[i]] = {};
                }
                old = parent;
                parent = parent[parts[i]];
            }
            if(fn){
                old[parts[--i]] = fn();
            }
            return F;
        }
    })();

    上面代码中,define和module分别是模块的定义和调用,模块被定义在闭包F上,通过getF()调用,这里用了old和parent2个对象是因为要保存当前模块的父级模块和祖父模块缓存,为了在依次添加完模块后,可以执行回调函数到当前模块上。

    这里的回调函数也就是一个js文件中要写的关于模块的构建,比如下面代码,包含了构造函数,以及静态方法;

    manger.getF().define('dom', function(){
        var dom = function(id){
            return document.getElementById(id);
        }
        dom.html = function(html){
            return this.innerHTML;
        }
        return dom;
    });

    调用一个模块

    F.module = function(mod,callback){
            var args = [].slice.call(arguments),
                fn = args.pop(),
                parts = args[0] && args[0] instanceof Array?args[0]:args,
                modules = [],
                modIds = '',
                i = 0,
                ilen = parts.length,
                parent, j, jlen;
            while(i < ilen){
                if(typeof parts[i] === 'string'){
                    parent = this;//this是模块缓存器
                    modIds = parts[i].replace('/^F./', '').split('.');//modIds=[]
                    for(j = 0, jlen = modIds.length; j < jlen; j++){
                        parent = parent[modIds[j]] || false;
                    }
                    modules.push(parent);//将模块对象装入modules中,在回调函数中应用
                }else{
                    modules.push(parts[i]);
                }
                i++;
            }
            fn.apply(null, modules);
        }

    通过将模块对象引入到回调函数中来执行模块的调用,关键是解析模块路径,把需要加载的模块都放入到modules数组中实现。例子如下

    manger.getF().module(['dom'],function(dom){
        dom('div1');
    });

    二 异步加载模式

    前言

    从上面的同步加载模式来看,模块开始就被加载到了内存中,没有异步在“运行时”动态绑定script标签来加载模块,那么问题来了如何引用别人没有写完的js模块文件,这就产生了一个异步加载问题,当前各大模块加载框架都是这么干的

    集定义和调用于一身的模块加载器

    F.module = function(url, modDeps, modCallback){
        var args = [].slice.call(arguments);
        var callback = args.pop();
        var deps = (args.length && (args[args.length - 1] instanceof Array)) ? args
                .pop() : [];
        var url = args.length ? args.pop() : null, 
                params = [], // 依赖模块序列,这个参数比较重要,它存放了模块依赖对象
                depsCount = 0, // 依赖模块计数器用来等待加载依赖模块
                i = 0;// 依赖模块下标
        var len;
        if (len = deps.length) {
            while(i<len){//依次加载依赖模块
                (function(i){//这个函数在定义的时候已经被执行了,所以首次加载一个模块会执行这个函数,而不是setModule
                    depsCount++;
                    loadModule(deps[i],function(mod){
                        params[i] = mod;//构造函数创建的模块对象传递给params【i】
                        depsCount--;
                        if(depsCount===0){//等待所有的依赖模块都加载到内存中,才一次性修改该模块的属性
                            setModule(url,params,callback);
                        }    
                    });
                })(i);
                /*depsCount++;
                loadModule(deps[i],function(mod){
                    params[i] = mod;//构造函数创建的模块对象传递给params【i】
                    depsCount--;
                    if(depsCount===0){//等待所有的依赖模块都加载到内存中,才一次性修改该模块的属性
                        setModule(url,params,callback);
                    }    
                });*/
                i++;
            }
        }else{//这是定义一个没有依赖的模块,直接执行回调函数
            setModule(url,[],callback);//setModule('lib/event',[],fn)
        }
    }

    上面代码中关键的一点就是用到了闭包来保存i,如果不使用闭包,i的值只会是最后依赖模块的数量,而不是每次的结果。关于闭包问题可以看我的博客

    代码中用到了2个重要的函数就是loadModule和setModule,下面对这2个代码一些解释

    loadModule = function(name, callback){
        var module;
    
        if (moduleCache[name]) {//模块已经在内存中
            _module = moduleCache[name];
            if (_module.status = 'loaded') {//模块加载完成
                //如果模块已经加载到页面中,立即执行模块的构造函数,并且将构造函数创建的模块对象传递给params[i]
                setTimeout(callback(_module.exports), 0);
            } else {//模块加载完,但是还未执行其中的回调函数,此时只是将回调函数放入到onload数组中去,没有执行回调
                _module.onload.push(callback);
            }
        } else {//首次加载该模块,设置模块的状态等等,onload只是一个含有一个元素的数组,存放了回调函数的引用,但是没有执行回调函数
            moduleCache[name] = {
                name : name,
                status : 'loading',
                exports : null,
                onload : [ callback ]
            };
            loadScript(getUrl(name));//将js文件加载到内存中
        }
        for ( var i in moduleCache) {
            console.log(moduleCache[i]);
        }
    };

    loadModule函数加载模块的时候分为3种情况分别是,在代码注释中也写了,关键的一点就是,在js模块中,如果js文件的状态是“loaded”那么立即执行callback回调函数,这里使用了settimeout延迟为0毫秒的作用不一定是立即执行,而是等到下一个trick才执行,在前端可以理解为立即执行,并且callback函数给他传递了这个模块的对外接口,这里指的是创建模块时候函数return的对象。

    // 修正js文件,并且执行js的回调函数,params是回调函数的参数,也就是所有依赖模块对象
    setModule = function(name, params, callback){
        var _module, fn;
        if (moduleCache[name]) {//模块(要执行的模块)缓存中已经存在了该模块,当前页面中已经加载了js文件
            _module = moduleCache[name];
            _module.status = 'loaded';
            _module.exports = callback ? callback.apply(_module, params) : null;//exports中存放着该模块的方法
            while (fn = _module.onload.shift()) {
                console.log('模块回调函数:'+fn);
                console.log('模块接口:'+_module.exports);
                for(var i in _module.exports){
                    console.log(i);
                }
                fn(_module.exports);
            }
        } else {//匿名模块,直接执行回调函数,也就是模块的调用
            console.log('匿名模块:'+callback);
            for(var i in params){
                console.log('参数'+i+':'+params[i]);
                if(typeof params[i]==='object'){
                    for(var j in params[i]){
                        console.log(j+':'+params[i][j]);
                    }
                }
            }
            callback && callback.apply(null, params);
        }
    },

     从F.module可以看到,只有在依赖模块都加载到内存中才使用setModule来修正模块,该函数的作用是将模块属性(moduleName,status,exports,onload)都修正为加载完成后的状态,并且执行回调函数。这里执行的回调函数和loadModule执行的回调函数不同,loadModule是要执行依赖模块的会掉,而这里是执行最终模块的回调。

    这样,一个完整的模块加载器就实现了,并且上面的异步加载器类似于RequireJS ,因为在有关于依赖的时候是提前定义的,并且是一旦定义就必须加载,不符合就近声明原则,所以有待优化的地方,还望批评指正。

  • 相关阅读:
    LNMP一键安装
    IIS出现问题报CS0016
    如何在windows live Write中添加插件
    合同管理系统 功能一览表
    房地产合同档案分类及编号规则
    属性ErrorLogFile不可用于JobServer的解决方案
    Wps定义选中区域的名称
    常用的SQL语句
    xp sp3 访问IIS元数据库失败解决办法
    完全卸载oracle11g步骤
  • 原文地址:https://www.cnblogs.com/bdbk/p/5084154.html
Copyright © 2011-2022 走看看