JavaScript设计模式(三十四):死心眼-同步模块模式

简介: 死心眼-同步模块模式

同步模块模式——SMD(Synchronous Module Definition)

模块化:将复杂的系统分解成高内聚、低耦合的模块,使系统开发变得可控、可维护、可拓展,提高模块的复用率

请求发出后,无论模块是否存在,立即执行后续的逻辑,实现模块开发中对模块的立即引用

创建一个导航模块

image.png

<style>
    #nav {
    
    
        list-style: none;
        margin: 0;
        padding: 0;
    }

    #nav li {
    
    
        float: left;
        width: 80px;
        height: 32px;
        line-height: 32px;
        text-align: center;
        background-color: #eee;
        cursor: pointer;
        color: #0051ff;
        margin: 0 2px;
    }

    #nav li span {
    
    
        margin-left: 3px;
        color: red;
        font-size: 12px;
    }

    #panel {
    
    
        display: none;
        position: absolute;
        top: 40px;
        width: 200px;
        height: 150px;
        line-height: 150px;
        text-align: center;
        background-color: #ccc;
    }
</style>

<body>
    <ul id="nav"></ul>
    <div id="panel">面板</div>
</body>
/**
 * 创建导航逻辑(由A工程师创建)
 * @param {HTMLElement} dom 上下文DOM
 * @param {Array} data 数据
 */
function createNav(dom, data) {
   
   
    /* ============== A工程师加入 创建了导航模块并添加了导航的内容 ============== */
    {
   
   
        for (let i = 0; i < data.length; i++) {
   
   
            dom.append(`<li>${
     
     data[i].label}</li>`)
        }
    }

    /* ============== B工程师加入 在A工程师的代码里边为导航添加引导 ============== */
    {
   
   
        let li = $('li', dom);
        for (let i = 0; i < data.length; i++) {
   
   
            if (data[i].isGuide) {
   
   
                $(li[i]).append('<span>[hot]</span>');
            }
        }
    }

    /* ============== C工程师加入 在A工程师的代码里边为导航添加事件 ============== */
    {
   
   
        let li = $('li', dom);
        li.on('mouseover', function () {
   
   
            $('#panel').css({
   
    left: this.offsetLeft + 'px', display: 'block' });
        }).on('mouseout', function () {
   
   
            $('#panel').css({
   
    display: 'none' });
        });
    }
};

// 模拟请求,获取导航引导数据
setTimeout(function () {
   
   
    let data = [
        {
   
    label: '首页', isGuide: false },
        {
   
    label: '推荐', isGuide: true },
        {
   
    label: '我的', isGuide: false }
    ];
    // 创建的导航模块
    createNav($('#nav'), data);
}, 2000);
  • 缺点
    • 由于B、C工程师是在A工程师写的代码的内部添加的自己的逻辑,当后续需求要求A工程师修改逻辑时,就很有可能影响B、C工程师的逻辑,导致A工程时修改时要照顾B、C写的逻辑。
  • 解决方案
    • 使用模块化开发(包括模块管理器和模块调用器)

模块管理器

// 模块管理器
const F = {
   
   };

/**
 * 定义模块管理器
 * @param {string} str 模块路由
 * @param {Function} fn 模块方法
 * @returns F模块管理器对象
 */
F.define = function (str, fn) {
   
   
    // 过滤掉第一个元素为F的字符串数组
    let keys = str.replace(/^F\./, '').split('.');
    // 屏蔽对define与module模块方法的重写
    if (keys[0] === 'define' || keys[0] === 'module') return this;
    // 遍历字符串数组,赋值到当前对象内
    let obj = this;
    for (let i = 0; i < keys.length; i++) {
   
   
        // 防止后续模块覆盖以前定义的模块
        if (typeof obj[keys[i]] === 'undefined') {
   
   
            obj[keys[i]] = {
   
   };
            if (i === keys.length - 1) {
   
   
                obj[keys[i]] = fn && fn();
            }
        }
        obj = obj[keys[i]];
    }
    // 当前对象
    return this;
};
// 创建一个字符串模块
F.define('F.string.fn', function () {
   
   
    return {
   
   
        // 定义一个去除字符串空格的方法
        trim(str) {
   
   
            return str.replace(/\s+/g, '');
        }
    };
});
F.string.fn.trim(' P r o s p e r  L e e '); // 'ProsperLee'

F.define('create.dom', function () {
   
   
    let dom = null;
    return function (el) {
   
   
        dom = document.createElement(el);
        dom.innerHTML = `创建的DOM元素-${
     
     el}`;
        document.body.appendChild(dom);
        return dom;
    }
});
F.create.dom('li'); // <li>创建的DOM元素-li</li>

F.define('a', function () {
   
    return function () {
   
    } });
F.define('a.b', function () {
   
    return function () {
   
    } });
F.define('a.b.c', function () {
   
    return function () {
   
    } });
F.define('a.b.c', function () {
   
    return {
   
    a: {
   
   }, b: {
   
   }, c: {
   
   }, d: {
   
   }, e: {
   
   } } });

模块调用器

/**
 * 模块调用方法
 * @param  {...any} args 需要最后的参数为function
 */
F.module = function (...args) {
   
   
    // 依赖模块列表
    let modules = [];
    // 判断最后一个参数是否为函数
    let fn = typeof args[args.length - 1] === 'function' ? args.pop() : null;
    // 将参数转换为数组
    let arr = (args.length === 1 && Array.isArray(args[0])) ? args[0] : args;
    // 遍历参数
    let i = 0;
    while (i < arr.length) {
   
   
        // 判断参数是否是字符串(通过define创建的)
        if (typeof arr[i] === 'string') {
   
   
            let keys = arr[i].replace(/^F\./, '').split('.');
            // 获取当前F对象
            let mod = this;
            for (let j = 0; j < keys.length; j++) {
   
   
                // 替换当前的mod,查询到最里层对象
                mod = mod[keys[j]];
            }
            modules.push(mod);
        } else {
   
   
            modules.push(arr[i])
        }
        i++;
    }
    fn && fn.apply(this, modules);
};
F.module(['create.dom', 'F.string.fn.trim', document], function (dom, trim, doc) {
   
   
    console.log(this); // F
    dom('li').innerText = trim(' P r o s p e r  L e e '); // <li>ProsperLee</li>
    console.log(doc); // #document
});

解决创建导航模块问题

image.png

// 模块管理器
const F = {
   
   };

/**
 * 定义模块管理器
 * @param {string} str 模块路由
 * @param {Function} fn 模块方法
 * @returns F模块管理器对象
 */
F.define = function (str, fn) {
   
   
    // 过滤掉第一个元素为F的字符串数组
    let keys = str.replace(/^F\./, '').split('.');
    // 屏蔽对define与module模块方法的重写
    if (keys[0] === 'define' || keys[0] === 'module') return this;
    // 遍历字符串数组,赋值到当前对象内
    let obj = this;
    for (let i = 0; i < keys.length; i++) {
   
   
        // 防止后续模块覆盖以前定义的模块
        if (typeof obj[keys[i]] === 'undefined') {
   
   
            obj[keys[i]] = {
   
   };
            if (i === keys.length - 1) {
   
   
                obj[keys[i]] = fn && fn();
            }
        }
        obj = obj[keys[i]];
    }
    // 当前对象
    return this;
};

/**
 * 模块调用方法
 * @param  {...any} args 需要最后的参数为function
 */
F.module = function (...args) {
   
   
    // 依赖模块列表
    let modules = [];
    // 判断最后一个参数是否为函数
    let fn = typeof args[args.length - 1] === 'function' ? args.pop() : null;
    // 将参数转换为数组
    let arr = (args.length === 1 && Array.isArray(args[0])) ? args[0] : args;
    // 遍历参数
    let i = 0;
    while (i < arr.length) {
   
   
        // 判断参数是否是字符串(通过define创建的)
        if (typeof arr[i] === 'string') {
   
   
            let keys = arr[i].replace(/^F\./, '').split('.');
            // 获取当前F对象
            let mod = this;
            for (let j = 0; j < keys.length; j++) {
   
   
                // 替换当前的mod,查询到最里层对象
                mod = mod[keys[j]];
            }
            modules.push(mod);
        } else {
   
   
            modules.push(arr[i])
        }
        i++;
    }
    fn && fn.apply(this, modules);
};
// 创建导航模块
F.define('nav.create', function () {
   
   
    return function (dom, data) {
   
   
        for (let i = 0; i < data.length; i++) {
   
   
            dom.append(`<li>${
     
     data[i].label}</li>`)
        }
    }
});

// 添加导航引导模块
F.define('nav.create.addGuide', function () {
   
   
    return function (dom, data) {
   
   
        let li = $('li', dom);
        for (let i = 0; i < data.length; i++) {
   
   
            if (data[i].isGuide) {
   
   
                $(li[i]).append('<span>[hot]</span>');
            }
        }
    }
});

// 添加导航事件模块
F.define('nav.create.hover', function () {
   
   
    return function (dom) {
   
   
        let li = $('li', dom);
        li.on('mouseover', function () {
   
   
            $('#panel').css({
   
    left: this.offsetLeft + 'px', display: 'block' });
        }).on('mouseout', function () {
   
   
            $('#panel').css({
   
    display: 'none' });
        });
    }
});

// 导航数据请求模块
F.define('nav.request', function () {
   
   
    return function (cb) {
   
   
        setTimeout(function () {
   
   
            let data = [
                {
   
    label: '首页', isGuide: false },
                {
   
    label: '推荐', isGuide: true },
                {
   
    label: '我的', isGuide: false }
            ];
            cb($('#nav'), data);
        }, 2000);
    }
});

console.log(F);
F.module(['nav.request', 'nav.create', 'nav.create.addGuide', 'nav.create.hover'], function (request, create, addGuide, hover) {
   
   
    request(function (dom, data) {
   
   
        create(dom, data);
        addGuide(dom, data);
        hover(dom);
    });
});

特点

模块化开发即是以分而治之的思想,实现对复杂系统的分解,使系统随着其功能的增加而变得可控、可拓展、可维护。

模块化开发是一种对系统的分解,但使用时又像是以组合模式对模块的组合。

因此这也使得系统中的问题一般出现在局部,使得开发人员处理相应模块即可,而不用顾虑整个系统。

因此相对于整个复杂的系统,对于局部模块的改造、优化甚至替换所需成本要小得多。

组合的灵活性也使得我们可以实现更复杂、多样化的功能。

目录
相关文章
|
2月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
142 11
|
12天前
|
JavaScript 前端开发 Docker
如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
通过这些步骤,可以确保您的Next.js应用在多核服务器上高效运行,并且在Docker环境中实现高效的容器化管理。
72 44
|
25天前
|
设计模式
「全网最细 + 实战源码案例」设计模式——模式扩展(配置工厂)
该设计通过配置文件和反射机制动态选择具体工厂,减少硬编码依赖,提升系统灵活性和扩展性。配置文件解耦、反射创建对象,新增产品族无需修改客户端代码。示例中,`CoffeeFactory`类加载配置文件并使用反射生成咖啡对象,客户端调用时只需指定名称即可获取对应产品实例。
88 40
|
26天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——简单工厂模式
简单工厂模式是一种创建型设计模式,通过工厂类根据传入参数创建不同类型的对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。适用于对象种类较少且调用者无需关心创建细节的场景。
54 19
|
1月前
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
90 33
|
24天前
|
设计模式 Java
「全网最细 + 实战源码案例」设计模式——生成器模式
生成器模式(Builder Pattern)是一种创建型设计模式,用于分步骤构建复杂对象。它允许用户通过控制对象构造的过程,定制对象的组成部分,而无需直接实例化细节。该模式特别适合构建具有多种配置的复杂对象。其结构包括抽象建造者、具体建造者、指挥者和产品角色。适用于需要创建复杂对象且对象由多个部分组成、构造过程需对外隐藏或分离表示与构造的场景。优点在于更好的控制、代码复用和解耦性;缺点是增加复杂性和不适合简单对象。实现时需定义建造者接口、具体建造者类、指挥者类及产品类。链式调用是常见应用方式之一。
52 12
|
26天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——工厂方法模式
简单工厂模式是一种创建型设计模式,通过一个工厂类根据传入参数创建不同类型的产品对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。适用于创建对象种类较少且调用者无需关心创建细节的场景。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。
45 15
|
2月前
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。
|
3月前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
57 2
|
3月前
|
设计模式 安全 Java
Kotlin - 改良设计模式 - 构建者模式
Kotlin - 改良设计模式 - 构建者模式

热门文章

最新文章