同步模块模式
同步模块模式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