返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript 设计模式之洋葱模型原理及实践应用
  • 473
分享到

JavaScript 设计模式之洋葱模型原理及实践应用

2024-04-02 19:04:59 473人浏览 安东尼
摘要

目录前言洋葱模型实践总结前言 先来听听一个故事吧,今天产品提了一个业务需求:用户在一个编辑页面,此时用户点击退出登录,应用需要提示用户当前有编辑内容未保存,是否保存;当用户操作完毕后

前言

先来听听一个故事吧,今天产品提了一个业务需求:用户在一个编辑页面,此时用户点击退出登录,应用需要提示用户当前有编辑内容未保存,是否保存;当用户操作完毕后再提示用户是否退出登录。

流程如下:

因为退出登录是属于公共部分由另一位同学维护,此时和他交流后“善良”的把需求仍给了他。并告知他可以通过某某方法获取我当前是否有编辑内容。然后我继续摸鱼,他开始疯狂输出

const handlerLoGout = async () => {
    if (window.location.href === 'xxx') {
        if (getEditState() === 'xxx') {
            await editConfirm()
        }
    }
    await logoutConfirm();
}

功能如约上线,新需求也如约到达:产品期望用户在VIP充值页面退出登录的时候,先弹出一个VIP充值广告,当用户关闭广告后再提示用户是否退出登录。

流程如下:

然后熟悉的场景、熟悉的人,在一番交流过后,那位同学略微暴躁的又开始疯狂输出,然后我继续摸鱼

const pages = {
    editPage: async () => {
        if (getEditState() === 'xxx') {
            await editConfirm()
        }
    },
    vipPage: async () => {
        if (getUserVipState() === 'xxx') {
            await vipConfirm()
        }
    }
}
const handlerLogout = async () => {
    const curPage = getPage();
    await pages[curPage];
    await logoutConfirm();
}

然后的然后功能又如约上线,然后需求又来了,一个场景中有多个弹窗业务,优先级不同,如果弹窗1不满足弹出条件,就使用弹窗2依此类推。众所周知产品的需求怎么做的完,他终于受不了了,开始思考怎么样自己才能摸摸鱼。与似乎不好的想法油然而生,如果自己维护的退出登录就只关注处理退出登录的业务,而其他业务的各种弹窗让业务方自己去处理那我就可以摸鱼啦。想法有了,拆解一下逻辑,底层逻辑就是在触发时需要有很多中间层的处理,等中间层处理完成后再处理自己的。那这不就像是洋葱模型吗。

洋葱模型

提到洋葱模型,koa的实现简单且优雅。koa中主要使用koa-compose来实现该模式。核心内容只有十几行,但是却涉及到高阶函数、闭包、递归、尾调用优化等知识,不得不说非常惊艳没有一行是多余的。简单来说,koa-compose暴露出一个compose方法,该方法接受一个中间件数组,并返回一个Promise函数。源码如下

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }
  
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

源码中compose主要做了三件事

  • 第一步:进行入参校验
  • 第二步:返回一个函数,并利用闭包保存middleware和index的值
  • 第三步:调用时,执行dispatch(0),默认从第一个中间件执行

dispatch函数的作用(dispatch其实就是next函数)

  • 第一步:通过i <= index来避免在同一个中间件中连续next调用
  • 第二步:设置index的值为当前中间件位置的值,并且拿到当前中间件函数
  • 第三步:判断当前是否还有中间件,没有返回Promise.resolve()
  • 第四步:返回Promise.resolve并把当前中间件执行结果做为返回,且传入context和next(dispatch)方法。这里利用尾调优化,避免了fn重新创建新的栈帧,同时提升了速度和节省了内存(大佬就是大佬)

我们可以通过其测试用例了解到执行的过程,有条件的读者可以通过下载源码进行断点调试,更能理解每一步的过程

  it('should work', async () => {
    const arr = []
    const stack = []
    stack.push(async (context, next) => {
      arr.push(1) // 步骤1
      await wait(1) // 步骤2
      await next() //  步骤3
      await wait(1) // 步骤14
      arr.push(6) // 步骤15
    })
    stack.push(async (context, next) => {
      arr.push(2) // 步骤4
      await wait(1) // 步骤5
      await next() // 步骤6
      await wait(1) // 步骤12
      arr.push(5) // 步骤13
    })
    stack.push(async (context, next) => {
      arr.push(3) // 步骤7
      await wait(1) // 步骤8
      await next() // 步骤9
      await wait(1) // 步骤10
      arr.push(4) // 步骤11
    })
    await compose(stack)({})
    expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))
  })

compose接收一个参数,该参数是一个Promise数组,注入中间件后返回了一个执行函数并执行。此时会按照上诉我标记的步骤进行执行。配置koa文档中的gif示例和流程图更好理解。通过不断的递归加上Promise链式调用完成了整个中间件的执行

实践

已经了解到洋葱模型的设计,按照当前摸鱼的诉求,期望stack.push这部分内容由业务方自己去注入,而退出登录只需要执行compose(stack)({})即可,额外诉求是项目中期望对弹窗有优先级的处理,那就是不是谁先进入谁先执行。对此改造一下middleware定义,新增level表示优先级后续它进行排序,优先级越高设置level值越高即可。

type Middleware<T = unknown> = {
  level: number;
  middleware: (context: T | undefined, next: () => Promise<any>) => void;
};

因为我们需要提供给业务方一个接口来添加中间件,这里使用类来实现,通过暴露出add和remove方法对中间件进行添加和删除,利用add方法在添加时利用level对中间件进行排序,使用stack来保存已经排序好的中间件。dispatch通过CV大法实现

class Scheduler<T> {
  stack: Middleware<T>[] = [];
  add(middleware: Middleware<T>) {
    const index = this.stack.findIndex((it) => it.level <= middleware.level);
    this.stack.splice(index === -1 ? this.stack.length : index, 0, middleware);
    return () => {
      this.remove(middleware);
    };
  }
  remove(middleware: Middleware<T>) {
    const index = this.stack.findIndex((it) => it === middleware);
    index > -1 && this.stack.splice(index, 1);
  }
  dispatch(context?: T) {
    // eslint-disable-next-line
    const that = this;
    let index = -1;
    return mutate(0);
    function mutate(i: number): Promise<void> {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'));
      index = i;
      const fn = that.stack[i];
      if (index === that.stack.length) return Promise.resolve();
      try {
        return Promise.resolve(fn.middleware(context, mutate.bind(null, i + 1)));
      } catch (error) {
        return Promise.reject(error);
      }
    }
  }
}
export default Scheduler;

然后修改业务中的处理,之后再加类似需求就可以摸鱼了。

// 暴露一个logoutScheduler方法
export const logoutScheduler = new Scheduler();
const handleLogout = () => {
    logoutScheduler.dispatch().then(() => {
        logoutConfirm();
    })
}
// 编辑页面
logoutScheduler.add({
    level: 2,
    middleware: async (_, next) => {
        if (getEditState() === 'xxx') {
          await editConfirm()
        }
        await next();
    }
})
// vip页面
logoutScheduler.add({
    level: 2,
    middleware: async (_, next) => {
        if (getUserVipState() === 'xxx') {
            await vipConfirm()
        }
        await next();
    }
})

总结

一个好的设计能在实际开发中更好的去解耦业务,而好的设计需要我们去阅读那些优秀的源码去学习和理解才能为我们所用。

以上就是javascript 设计模式之洋葱模型原理及实践应用的详细内容,更多关于JavaScript 设计模式洋葱模型的资料请关注编程网其它相关文章!

--结束END--

本文标题: JavaScript 设计模式之洋葱模型原理及实践应用

本文链接: https://lsjlt.com/news/166740.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • JavaScript 设计模式之洋葱模型原理及实践应用
    目录前言洋葱模型实践总结前言 先来听听一个故事吧,今天产品提了一个业务需求:用户在一个编辑页面,此时用户点击退出登录,应用需要提示用户当前有编辑内容未保存,是否保存;当用户操作完毕后...
    99+
    2024-04-02
  • JavaScript设计模式之原型模式怎么实现
    本篇内容主要讲解“JavaScript设计模式之原型模式怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript设计模式之原型模式怎么实现”吧!前言设计模式呢最多的可能是用到类...
    99+
    2023-07-02
  • Javascript设计模式之原型模式详细
    目录1、原型模式示例一示例二示例三2、观察者模式1、原型模式 原型模式用于在创建对象时,通过共享某个对象原型的属性和方法,从而达到提高性能、降低内存占用、代码复用的效果。 示例一 ...
    99+
    2024-04-02
  • JavaScript设计模式之原型模式详情
    目录前言案例回顾原型的拓展前言 设计模式呢最多的可能是用到类,我们去通过类来封装一些实用的方法,通过设计模式去实现各个方法之间的解耦等,由于JS中的继承是用原型链继承的,所以原型模式...
    99+
    2024-04-02
  • 深入理解Java设计模式之原型模式
    目录一、前言二、什么是原型模式三、原型模式的适用场景四、原型模式的实现1.浅拷贝实现2.深拷贝实现五、总结一、前言 单例模式可以避免重复创建消耗资源的对象,但是却不得不共用对象。若是...
    99+
    2024-04-02
  • javascript设计模式之代理模式
    目录一. 初识代理模式二. 代理模式的实现思想三. 代理模式分类四. 虚拟代理模式的实际运用五. 代理的使用意义及要求六. 总结一. 初识代理模式 代理模式是为一个对象提供一个代用品...
    99+
    2024-04-02
  • Java设计模式之原型模式详解
    目录一、前言二、优点及适用场景三、原型模式的注意事项四、浅复制和深复制五、浅复制demo演示六、深复制demo演示一、前言 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口...
    99+
    2024-04-02
  • .Net创建型设计模式之原型模式(Prototype)
    目录一、动机(Motivation)二、意图(Intent)三、结构(Structure)四、模式的组成五、 原型模式的具体实现六、原型模式的实现要点:1、原型模式的优点:2、原型模...
    99+
    2024-04-02
  • Android常用设计模式之原型模式详解
    目录前言一、基本使用二、对象与集合的使用三、浅拷贝与深拷贝四、Kotlin的应用总结前言 什么是原型模式? 它是指创建对象的种类,并通过拷贝这些原型创建新的对象。 它是用于创建重复的...
    99+
    2022-11-13
    Android 设计模式原型模式 Android 原型模式
  • Java 深入理解创建型设计模式之原型模式
    1.思考问题 现在有一只羊 tom,姓名为: tom,年龄为:1,颜色为:白色,请编写程序创建和 tom羊属性完全相同的10只羊。 按照传统的思路来,我们可能会按照下面的方式去写。 ...
    99+
    2024-04-02
  • JavaScript设计模式之原型模式和适配器模式示例详解
    目录原型模式原型模式介绍代码实现适配器模式适配器模式介绍代码实现小结原型模式 原型模式介绍 原型模式是指原型实例指向创建对象的种类,并通过拷贝这些原型创建新的对象,是一种用来创建对象...
    99+
    2024-04-02
  • Java设计模式之java原型模式详解
    目录介绍角色Java语言提供的clone()方法代码演示—克隆羊结论深浅拷贝深浅拷贝探讨实现深克隆的方式一 : 手动对引用对象进行克隆实现深克隆的方式一 :序列化原型模式对单例模式的...
    99+
    2024-04-02
  • Java设计模式之代理模式原理及实现代码分享
    简介Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术。生活中的方方面面都可以虚拟到代码中。代理模式所讲的就是现实生活中的这么一个概念:中介。代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。...
    99+
    2023-05-30
    java 设计模式 代理模式
  • JavaScript设计模式学习之代理模式
    目录概述实现方法保护代理虚拟代理虚拟代理实现图片懒加载概述 代理模式属于设计模式中结构型的设计模式; 定义: 顾名思义就是为一个对象提供一个代用品或占位符,以便控制对它的访问! 白话...
    99+
    2024-04-02
  • Android 适配器模式应用及设计原理
    适配器模式是一种重要的设计模式,在 Android 中得到了广泛的应用。适配器类似于现实世界里面的插头,通过适配器,我们可以将分属于不同类的两种不同类型的数据整合起来,而不必去...
    99+
    2022-06-06
    适配器模式 Android
  • Golang设计模式之原型模式详细讲解
    目录原型模式概念示例原型模式 原型是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。 所有的原型类都必须有一个通用的接口, 使得即使在对象...
    99+
    2023-01-11
    Go原型模式 Go设计模式
  • PHP设计模式的应用与实践
    php 中设计模式是一种可重用的解决方案,用于解决常见的编程问题。它分为三大类:创建型模式、结构型模式和行为型模式。其中应用广泛的创建型模式包括工厂模式,用于创建不同类型的对象;结构型模...
    99+
    2024-05-16
    php 设计模式
  • java设计模式之代理模式怎么应用
    代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对实际对象的访问。代理对象充当了实际对象的替代品,可以在调用实际对象的...
    99+
    2023-08-08
    java
  • Java设计模式之原型模式的示例详解
    目录定义案例需求方案一方案二对比分析总结 定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 即实现了一个原型接口,该接口用于创建当前对象的克隆,当直接创建对象的代...
    99+
    2024-04-02
  • Java 深入探讨设计模式之原型模式篇
    目录传统方式原型模式基本介绍原型模式在spring框架中源码分析深入讨论-浅讨论和深拷贝原型模式的注意事项和细节传统方式 克隆羊问题 现在有一只羊 tom,姓名为: tom,年龄为:...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作