javascript设计模式——同步模块模式和异步模块模式、widget设计模式

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

同步模块模式

test.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="smd.js"></script>
</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>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="amd.js"></script>
</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.onload.push(callback) ;
        }
    // 模块第一次被依赖引用   
    }else {
        // 缓存该模块初始化信息
        moduleCache[moduleName] = {

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

/***
*   设置模块并执行模块构造函数
*   @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.onload.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。

 

widget设计模式

html页面代码如下:其中id=“demo_script”最为模板,di=“test”的div作为容器。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="amd.js"></script>
</head>
<body>
    <div id="test"></div>    
    <!-- srcpt模板内容 -->
    <script type="text/template" id="demo_script">
        <div id="tag_cloud">
            {% for(var i = 0; i < tagCloud.length; i++){ 
                var ctx = tagCloud[i] ; %}
            <a href="#" class="tag_item{% if(ctx['is_selected']){ %}selected{% } %}" title="{%=ctx['title']%}">{%=ctx['text']%}</a>
            {% } %}
        </div>
    </script>
</body>
</html>

amd.js文件同上,dom.js文件也同上。

另外需要一个处理模板的文件。放在lib/template.js

// 模板引擎模块
F.module('lib/template', function() {

    /***
    *   模板引擎,处理数据的编译模板入口
    *   @param  str     模块容器id或者模板字符串
    *   @param  data    渲染数据
    **/ 
    var _TplEngine = function(str, data) {
        // 如果数据是数组
        if(data instanceof Array) {
            // 缓存渲染模板结果
            var html = '' ;
            // 数据索引
            var i = 0 ;
            // 数据长度
            var len = data.length ;
            // 遍历数据
            for(; i < len; i++) {
                // 缓存模板渲染结果,也可以写成
                // html += arguments.callee(str, data[i]) ;
                html += _getTpl(str) (data[i]) ;
            }
            // 返回模板渲染最终结果
            return html ;
        }else {
            // 返回模板渲染结果
            return _getTpl(str)(data) ;
        }
    } ;
    /***
    *   获取模板
    *   @param  str 模板容器id,或者模板字符串
    **/
    var _getTpl = function(str) {
        // 获取元素
        var ele = document.getElementById(str) ;
        // 如果元素存在
        if(ele) {
            // 如果是input或者textarea表单元素,则获取该元素的value值,否则获取元素的内容
            var html = /^(textarea | input)$/i.test(ele.nodeName) ? ele.value : ele.innerHTML ;
            // 编译模板
            return _compileTpl(html) ;
        }else {
            // 编译模板
            return _compileTpl(str) ;
        }
    } ;
    // 处理模板
    var _dealTpl = function(str) {
        // 左分隔符
        var _left = '{%' ;
        // 右分隔符
        var _right = '%}' ;
        // 显示转化为字符串
        return String(str)
            // 转义标签内的<如:<div>{%if(a&lt;b)%}</div> -> <div>{%if(a<b)%}</div>
            .replace(/&lt;/g, '<')
            // 转义标签内的>
            .replace(/&gt;/g, '>')
            // 过滤回车符,制表符,回车符
            .replace(/[\r\t\n]/g, '')
            // 替换内容
            .replace(new RegExp(_left + '=(.*?)' + _right, 'g'), "',typeof($1) === 'undefined' ? '' : $1, '") 
            // 替换左分隔符
            .replace(new RegExp(_left, 'g'), "');")
            // 替换右分隔符
            .replace(new RegExp(_right, 'g'), "template_array.push('") ;

    } ;
    /***
    *   编译执行
    *   @param  str 模板数据
    **/ 
    var _compileTpl = function(str) {
        // 编译函数体
        var fnBody = "var template_array=[];\nvar fn=(function(data){\nvar template_key='';\nfor(key in data){\ntemplate_key +=('var '+key+'=data[\"'+key+'\"];');\n}\neval(template_key);\ntemplate_array.push('"+_dealTpl(str)+"');\ntemplate_key=null;\n})(templateData);\nfn=null;\nreturn template_array.join('') ;" ;
        // 编译函数
        return new Function('templateData',  fnBody) ;
    } ;

    // 返回
    return _TplEngine ;
}) ;

最后,console里的测试代码:

var data = {
        tagCloud : [
            {is_selected : true, title : '这是一本设计模式书', text : '设计模式'},
            {is_selected : false, title : '这是一本HTML', text : 'HTML'},
            {is_selected : null, title : '这是一本CSS', text : 'CSS'},
            {is_selected : '', title : '这是一本javascript', text : 'javascript'},
        ]
    }

F.module(['lib/template', 'lib/dom'], function(template, dom) {
        // 服务器端获取到data数据逻辑
        // 创建组件视图逻辑
        var str = template('demo_script', data) ;
        dom.html('test', str) ;
        // 组件其他交互逻辑
    }) ;

所以要用widget的话,只需要修改id=“demo_script”的script文件,然后提供对应的数据data,以及修改console里的function即可使用widget。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值