同步模块模式

同步模块模式SMD是请求发出后,无论模块是否存在,立即执行后续的逻辑,实现模块开发中对模块的立即引用,模块化是将复杂的系统分解为高内聚、低耦合模块,同步模块模式不属于一般定义的23种设计模式的范畴,而通常将其看作广义上的架构型设计模式。

描述

同步模块模式通常用来解决如下场景的问题,随着页面功能的增加,系统的业务逻辑越来越复杂,多人开发的功能经常耦合在一起,有时项目经理提出的需求,分配给多人实现的时候,常常因为某一处功能耦合了多人的代码,从而出现排队修改的现象。
通过使用模块化来分解复杂的系统可以很好的去解决这个问题,要想实现模块化开发,首先就需要有一个模块管理器,其管理着模块的创建与调度,对于模块的调用分为两类,第一类就是同步的模块调度,实现相对比较简单,不需要考虑模块间的异步加载,第二类的异步模块调度就比较繁琐,其可以实现对模块的加载调度。

实现

// 定义模块管理器单体对象
var F = F || {};
// 创建模块的方法define
// str 模块路由; fn 模块方法
F.define = function(str, fn){   // 定义模块方法,本应该在闭包中定义,这里先忽略
    let parts = str.split("."); // 解析模块路由str
    // 如果在闭包中,为了屏蔽对模块的直接访问,建议将模块添加给闭包内部私有变量
    // old,当前模块的祖父模块;parent,当前模块父模块
    let old = this;
    let parent = this;
    // i模块层级,len模块层级长度
    let i = 0;
    // 如果第一个模块是模块管理器单体对象,则移除
    if(parts[0] === "F") parts = parts.slice(1);
    // 屏蔽对define与module模块方法的重写
    if(parts[0] === "define" || parts[0] === "module") return void 0;
    // 遍历路由器并定义每层模块
    for(let len = parts.length; i < len; i++){
        // 如果父模块中不存在当前模块,声明当前模块
        if(parent[parts[i]] === void 0) parent[parts[i]] = {};
        // 缓存下一级的祖父模块
        old = parent;
        // 缓存下一级的父模块
        parent = parent[parts[i]];
    }
    // 如果给定模块方法fn则定义改模块方法
    if(fn){
        // 此时i等于parts.length,故减1
        old[parts[--i]] = fn();
    }
    return this;    // 返回模块管理器单体对象
}

// 用上面的方法来创建模块
// 创建模块k,并对该模块提供t方法
F.define("k", function(){
    return {
        t: function(){
            console.log("it is function t")
        }
    }
    //也可以以构造函数的方法返回
    /* let xx = function(){};
    xx.t = function(){
        console.log("this is xx.t")
    }
    xx.tt = function(){
        console.log("this is xx.tt")
    }
    return xx; */
});

// 使用t方法,但正式的模块开发不允许直接调用
// 一是因为模块通常为闭包中的私有变量,不会保存在F上,获取不到,这里简化没有使用闭包
// 二是因为这样调用不符合模块开发规范
F.k.t();
// 用构造函数返回时的调用方法
/* F.k.t();
F.k.tt(); */

// 也可先声明模块再定义方法
F.define("a.b")
F.a.b = function(){
    console.log("this is function from a.b")
}
F.a.b();

// 由于不能直接调用,就需要调用模块的方法
// 调用模块的方法module
// 参数分两部分,依赖模块与回调函数(最后一个参数)
// 原理是遍历获取所有依赖模块,并保存在依赖模块列表中,然后将这些依赖模块作为参数传入执行函数中执行
F.module = function(...args){
    let fn = args.pop();    // 获取回调执行函数
        // 获取依赖模块,若args[0]是数组,则它为依赖模块,否则为args
    let parts = args[0] && args[0] instanceof Array ? args[0] : args;
    let modules = [];   // 依赖模块列表
    let modIDs = "";    // 模块路由
    let i = 0;  // 依赖模块索引
    let ilen = parts.length;    // 依赖模块长度
    // 遍历依赖模块
    parts.forEach(v => {
        if(typeof v === "string"){   // 如果是模块路由
            let parent = this;  // 设置当前模块父对象(F)
            // 解析模块路由,并屏蔽掉模块父对象
            modIDs = v.replace(/^F./, "").split(".");
            // 遍历模块路由层级
            for(let j = 0; j < modIDs.length; j++){
                parent = parent[modIDs[j]] || false;    // 重置父模块
            }
            modules.push(parent);   // 将模块添加到模块依赖列表
        }else{  // 如果是模块对象
            modules.push(v); // 直接加入模块依赖列表
        }
    })
    fn.apply(null, modules);    // 执行回调函数
}

// 依赖dom和k模块的方法,数组形式
F.module(["dom", "k"], function(){
    console.log(1);
})
// 依赖dom2模块和k.a方法,字符串形式
F.module("dom2", "k.a", function(){
    console.log(2);
})

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.jianshu.com/p/2359390737aa https://www.dazhuanlan.com/2020/03/09/5e65fa05c9bb7/ https://blog.csdn.net/WuLex/article/details/107350493