来自:《 设计模式》(张容铭),自己留作备用,勿转。

同步模块模式

test.html文件:

<!DOCTYPE html>
<html lang=\"en\">
<head>
    <  charset=\"UTF-8\">
    <  name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
    <  http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">
    < >Document</ >
    <  src=\"smd.js\"></ >
</head>
<body>
    <div id=\"test\">test</div>
</body>
</html>

smd.js文件:

var F = F||{};
//定义模块管理器
F.define = function (str, fn) {
    var parts = str.split(\'.\'),
        old = parent = this,
        i = len = 0;
    if (parts[0] === \'F\') {
        parts = parts.slice();
    }
    if (parts[0] === \'define\' || parts[0] === \'modules\') {
        return;
    }
    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 this;
}

    // 创建模块
    // F.string模块
    F.define(\'string\', function() {
        // 接口方法
        return {
            // 清除字符串两边空白
            trim : function(str) {
                return str.replace(/^\\s+|\\s+$/g, \'\') ;
            }
        }
    }) ;

    F.define(\'dom\', function() {
        // 简化获取元素方法(重复获取可被替代,此设计用于演示模块添加)
        var $ = function(id) {
            $.dom = document.getElementById(id) ;
            // 返回构造函数对象
            return $ ;
        }
        // 获取或者设置元素内容
        $.html = function(html) {
            // 如果传参则设置元素内容,否则获取元素内容
            if(html) {
                this.dom.innerHTML = html ;
                return this ;
            }else {
                return this.dom.innerHTML ;
            }
        }
        // 返回构造函数
        return $ ;
    }) ;

    /*******************************************************************/
    /*
    对于模块的创建,也可以先声明后创建,比如添加addClass()为元素添加class方法。
    */
    // 为dom模块添加addClass方法
    // 注意:此种添加模式之所以可行,是因为将模块添加到F对象上,模块化开发中只允许上面的添加方式
    F.define(\'dom.addClass\') ;
    F.dom.addClass = (function(type, fn) {
        return function(className) {
            // 如果不存在该类
            if(!~this.dom.className.indexOf(className)) {
                // 简单添加类
                this.dom.className += \' \' + className ;
            }
        }
    })() ;

    /*******************************************************************/
    //模块调用方法---创建一个“使用”模块方法:module
    F.module = function() {
        // 将参数转化为数组
        var args = [].slice.call(arguments) ;
        // 获取回调执行函数
        var fn = args.pop() ;
        // 获取依赖模块,如果args[0]是数组,则依赖模块args[0]。否则依赖模块arg
        var parts = args[0] && args[0] instanceof Array ? args[0] : args ;
        // 依赖模块列表
        var modules = [] ;
        // 模块路由
        var modIDs = \'\' ;
        // 依赖模块索引
        var i = 0 ;
        // 依赖模块长度
        var ilen = parts.length ;
        // 父模块,模块路由层级索引,模块路由层级长度
        var parent, j, jlen ;
        // 遍历依赖模块
        while(i < ilen) {
            // 如果是模块路由
            if(typeof parts[i] === \'string\') {
                // 设置当前模块父对象(F)
                parent = this ;
                // 解析模块路由,并屏蔽掉模块父对象
                modIDs = parts[i].replace(/^F./, \'\').split(\'.\') ;
                // 遍历模块路由层级
                for(j = 0, jlen = modIDs.length; j < jlen; j++) {
                    // 重置父模块
                    parent = parent[modIDs[j]] || false ;
                }
                // 将模块添加到依赖模块列表中
                modules.push(parent) ;
            // 如果是模块对象  
            }else {
                // 直接加入依赖模块列表中
                modules.push(parts[i]) ;
            }
            // 取下一个依赖模块
            i++ ;
        }
        // 执行回调执行函数
        fn.apply(null, modules) ;
    }

console里的测试代码:

    // 调用模块
    // 引用dom模块与document对象(注意:依赖模块对象通常为已创建的模块对象)
     F.module([\'dom\', document], function(dom, doc) {
      // 通过dom模块设置元素内容
      dom(\'test\').html(\'new add!\') ;
      // 通过document设置body元素背景色
      doc.body.style.background = \'red\' ;
     }) ;

异步模块模式:

html文件:

<!DOCTYPE html>
<html lang=\"en\">
<head>
    <  charset=\"UTF-8\">
    <  name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
    <  http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">
    < >Document</ >
    <  src=\"amd.js\"></ >
</head>
<body>
    <button id=\"demo\">aaa</button>
    <button id=\"bbb\">bbb</button>
</body>
</html>

amd.js文件:

/**
    *   闭包环境 
    **/
    // 向闭包中传入模块管理其对象F(~屏蔽雅素偶文件时,前面漏写;报错) 
    ~(function(F) {
        // 模块缓存器。存储已创建模块
        var moduleCache = {} ;
    }) ((function() {
        // 创建模块管理器对象F,并保存在全局作用域中
        return window.F = {} ;
    }) ()) ;

    /**
    *   创建或调用模块方法
    *   @param  url             参数为模块URL
    *   @param  deps            参数为依赖模块 
    *   @param  callback        参数为模块主函数
    **/
   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() : [] ; 
    // 该模块URL(模块ID)
    var url = args.length ? args.pop() : null ;
    // 依赖模块序列
    var params = [] ;
    // 未加载的依赖模块数量统计
    var depsCount = 0 ;
    // 依赖模块序列中索引值
    var i = 0 ;
    // 依赖模块序列长度
    var len ;
    // 获取依赖模块长度
    if(len = deps.length) {
        // 遍历依赖模块
        while(i < len) {
            // 闭包保存i
            (function(i) {
                // 增加未加载依赖模块数量统计
                depsCount++ ;
                // 异步加载依赖模块
                loadModule(deps[i], function(mod) {
                    // 依赖模块序列中添加依赖模块接口引用
                    params[i] = mod ;
                    // 依赖模块加载完成,依赖模块数量统计减一
                    depsCount-- ;
                    // 如果依赖模块全部加载
                    if(depsCount === 0) {
                        // 在模块缓存器中矫正该模块,并执行构造函数
                        setModule(url, params, callback) ;
                    }
                }) ;
            }) (i) ;
            // 遍历下一依赖模块
            i++ ;
        }
    // 无依赖模块,直接执行回调函数   
    }else {
        // 在模块缓存器中矫正该模块,并执行构造函数
        setModule(url, [], callback) ;
    }
}

// 加载模块
var moduleCache = {} ;

/***
*   异步加载依赖模块所在文件
*   @param      moduleName      模块路径(id)
*   @param      callback        模块加载完成回调函数
**/
var loadModule = function(moduleName, callback) {
    // 依赖模块
    var _module ;
    // 如果依赖模块被要求加载过
    if(moduleCache[moduleName]) {
        // 获取该模块信息
        _module = moduleCache[moduleName] ;
        // 如果模块加载完成
        if(_module.status === \'loaded\') {
            // 执行模块加载完成回调函数
            setTimeout(callback(_module.exports), 0) ;
        }else {
            // 缓存该模块所处文件加载完成回调函数
            _module. .push(callback) ;
        }
    // 模块第一次被依赖引用   
    }else {
        // 缓存该模块初始化信息
        moduleCache[moduleName] = {

            moduleName : moduleName,        // 模块id
            status : \'loading\',             // 模块对应文件加载状态(默认加载中)
            exports : null,                 // 模块接口
              : [callback]             // 模块对应文件加载完成回调函数缓冲器
        };
        // 加载模块对应文件
        load (getUrl(moduleName)) ;
    }
} ;
// 获取文件路径
var getUrl = function(moduleName) {
    // 拼接完整的文件路径字符串,如\'lib/ajas\' => \'lib/ajax.js\'
    return String(moduleName).replace(/\\.js$/g, \'\') + \'.js\' ;
} ;
// 加载脚本文件
var load  = function(src) {
    var _  = document.createElement(\' \') ;
    // 文本类型
    _ .type = \'text/ \' ;
    // 确认编码
    _ .charset = \'UTF-8\' ;
    // 异步加载
    _ .async = true ;
    // 文件路径
    _ .src = src ;
    // 插入页面中
    document.getElementsByTagName(\'head\')[0].appendChild(_ ) ;
};

/***
*   设置模块并执行模块构造函数
*   @param   moduleName         模块id名称
*   @param   params             依赖模块
*   @param   callback           模块构造函数
**/ 
var setModule = function(moduleName, params, callback) {
    // 模块容器,模块文件加载完成回调函数
    var _module, fn ;
    // 如果模块被调用过
    if(moduleCache[moduleName]) {
        // 获取模块
        _module = moduleCache[moduleName] ;
        // 设置模块已经加载完成
        _module.status = \'loaded\' ;
        // 矫正模块接口
        _module.exports = callback ? callback.apply(_module, params) : null ;
        // 执行模块文件加载完成回调函数
        while(fn = _module. .shift()) {
            fn(_module.exports) ;
        }
    }else {
        // 模块不存在(匿名函数),则直接执行构造函数
        callback && callback.apply(null, params) ;
    }
};

lib/dom.js文件

F.module(\'lib/dom\', function() {
    return {
        // 获取元素方法
        g : function(id) {
            return document.getElementById(id) ;
        },
        // 获取或者设置元素内容方法
        html : function(id, html) {
            if(html) {
                this.g(id).innerHTML = html ;
            }else {
                return this.g(id).innerHTML ;
            }
        }
    }
}) ;

lib/event.js文件

F.module(\'lib/event\', [\'lib/dom\'], function(dom) {
    var events = {
        // 绑定事件
        on : function(id, type, fn) {
            dom.g(id)[\'on\' + type] = fn ;
        }
    } 
    return events;
}) ;

Console里的测试代码:

        F.module([\'lib/event\', \'lib/dom\'], function(events, dom) {
            events.on(\'demo\', \'click\', function() {
                dom.html(\'demo\', \'success\') ;
            }) ;
        }) ;

id为“demo”的button按钮被点击后,text变成了success。

收藏 打印