JS 设计模式

单例模式

一个类只有一个实例,并提供一个访问它的全局访问点。

 1 class LoginForm {
 2     constructor() {
 3         this.state = 'hide'
 4     }
 5     show() {
 6         if (this.state === 'show') {
 7             alert('已经显示')
 8             return
 9         }
10         this.state = 'show'
11         console.log('登录框显示成功')
12     }
13     hide() {
14         if (this.state === 'hide') {
15             alert('已经隐藏')
16             return
17         }
18         this.state = 'hide'
19         console.log('登录框隐藏成功')
20     }
21  }
22  
23  LoginForm.getInstance = (function () {
24      let instance
25      return function () {
26         if (!instance) {
27             instance = new LoginForm()
28         }
29         return instance
30      }
31  })()
32  
33 let obj1 = LoginForm.getInstance()
34 obj1.show()
35  
36 let obj2 = LoginForm.getInstance()
37 obj2.hide()
38  
39 console.log(obj1 === obj2)

优点:

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  • 单例模式设置全局访问点,可以优化和共享资源的访问。
  • 只会实例化一次。简化了代码的调试和维护

缺点:

  • 单例模式一般没有接口,扩展困难
  • 有可能导致模块间的强耦合 从而不利于单元测试。

工厂模式

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。统一由工厂类来决定具体实例化哪一个类。

 1 class Product1 {
 2     product() {
 3         console.log("生产一线");
 4     }
 5 }
 6 class Product2 {
 7     product() {
 8         console.log("生产二线");
 9     }
10 }
11 class Factory {
12     constructor() {
13         this.Product1 = Product1;
14         this.Product2 = Product2;
15     }
16     create(name, callBack) {
17         const product = new this[name]();
18         product.product();
19         return callBack("susess");
20     }
21 }
22 let p = new Factory();
23 p.create("Product1", (res) => {
24     console.log(res);
25 });

优点:

  • 工厂职责单一化易于维护
  • 有利于消除对象间的耦合,提供更大的灵活性

缺点:添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度。

代理模式

主要应用:

  • ES6 的 proxy 
  • HTML元 素事件代理

eg:

 1 1. 给"ul"标签添加点击事件
 2 2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
 3 注意:
 4 1. 必须使用DOM0级标准事件(onclick)
 5  
 6 target表示当前触发事件的元素
 7 currentTarget是绑定处理函数的元素
 8 只有当事件处理函数绑定在自身的时候,target才会和currentTarget一样
 9  
10 <ul>
11       <li>.</li>
12       <li>.</li>
13       <li>.</li>
14 </ul>
15  
16 <script type="text/javascript">
17     document.querySelector('ul').onclick=event=>{
18         event.target.innerText+='.'
19      }
20 </script>

装饰器模式

动态地给某个对象添加一些额外的职责,是一种实现继承的替代方案。

 1 // 首先我们要创建一个基类
 2 function Man(){
 3 
 4   this.def = 2;
 5   this.atk = 3; 
 6   this.hp = 3;
 7 }
 8 
 9 // 装饰者也需要实现这些方法,遵守 Man 的接口
10 Man.prototype={
11   toString:function(){
12     return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
13   }
14 }
15 // 创建装饰器,接收 Man 对象作为参数。
16 var Decorator = function(man){
17   this.man = man;
18 }
19 
20 // 装饰者要实现这些相同的方法
21 Decorator.prototype.toString = function(){
22     return this.man.toString();
23 }
24 
25 // 继承自装饰器对象
26 // 创建具体的装饰器,也是接收 Man 作对参数
27 var DecorateArmour = function(man){
28 
29   var moreDef = 100;
30   man.def += moreDef;
31   Decorator.call(this,man);
32 
33 }
34 DecorateArmour.prototype = new Decorator();
35 
36 // 接下来我们要为每一个功能创建一个装饰者对象,重写父级方法,添加我们想要的功能。
37 DecorateArmour.prototype.toString = function(){
38   return this.man.toString();
39 } 
40 
41 // 注意这里的调用方式
42 // 构造器相当于“过滤器”,面向切面的
43 var tony = new Man();
44 tony = new DecorateArmour(tony);
45 console.log(`当前状态 ===> ${tony}`);
46 // 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3

优点:

  • 方便动态的扩展功能,且提供了比继承更多的灵活性。

缺点:

  • 多层装饰比较复杂。

责任链模式

允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。

eg:

促销时针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用户会收到 100 元的商城优惠券,200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

接收一下三个参数:

  • orderType:表示订单类型(定金用户或者普通购买用户),code 的值为 1 的时候是 500 元定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户。
  • pay:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式。
  • stock:表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用户不受此限制。
 1 // 500 元订单
 2 var order500 = function (orderType, pay, stock) {
 3   if (orderType === 1 && pay === true) {
 4     console.log('500 元定金预购, 得到 100 优惠券');
 5   } else {
 6     order200(orderType, pay, stock); // 将请求传递给 200 元订单 
 7   }
 8 };
 9 // 200 元订单
10 var order200 = function (orderType, pay, stock) {
11   if (orderType === 2 && pay === true) {
12     console.log('200 元定金预购, 得到 50 优惠券');
13   } else {
14     orderNormal(orderType, pay, stock); // 将请求传递给普通订单 }
15   };
16 }
17 // 普通购买订单
18 var orderNormal = function (orderType, pay, stock) {
19   if (stock > 0) {
20     console.log('普通购买, 无优惠券');
21   } else {
22     console.log('手机库存不足');
23   }
24 };
25 // 测试结果:
26 order500(1, true, 500); // 输出:500 元定金预购, 得到 100 优惠券
27 order500(1, false, 500); // 输出:普通购买, 无优惠券
28 order500(2, true, 500); // 输出:200 元定金预购, 得到 500 优惠券
29 order500(3, false, 500); // 输出:普通购买, 无优惠券
30 order500(3, false, 0);// 输出:手机库存不足

优点:

  代码结构比较清晰

缺点:

  违反开放-封闭原则

应用:

  • JS 中的事件冒泡
  • 作用域链
  • 原型链

观察者模式:

eg: Promise  的执行类似于观察者模式

1 const p1 = new Promise((resolve, reject) => {
2     setTimeout(() => {
3         resolve('result')
4     },
5     1000);
6 }) 
7  
8 p1.then(res => console.log(res), err => console.log(err))

分析Promise的调用流程:

  • Promise的构造方法接收一个executor(),在new Promise()时就立刻执行这个executor回调
  • executor()内部的异步任务被放入宏/微任务队列,等待执行
  • then()被执行,收集成功/失败回调,放入成功/失败队列
  • executor()的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行

观察者模式:收集依赖 -> 触发通知 -> 取出依赖执行

在Promise里,执行顺序是 then收集依赖 -> 异步触发resolve -> resolve执行依赖

Promise实现:

 1 class MyPromise {
 2   // 构造方法接收一个回调
 3   constructor(executor) {
 4     this._resolveQueue = []    // then收集的执行成功的回调队列
 5     this._rejectQueue = []     // then收集的执行失败的回调队列
 6  
 7     /*由于resolve/reject是在executor内部被调用, 
 8     因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue*/
 9  
10     let _resolve = (val) => {
11       // 从成功队列里取出回调依次执行
12       while(this._resolveQueue.length) {
13         const callback = this._resolveQueue.shift()
14         callback(val)
15       }
16     }
17     // 实现同resolve
18     let _reject = (val) => {
19       while(this._rejectQueue.length) {
20         const callback = this._rejectQueue.shift()
21         callback(val)
22       }
23     }
24     // new Promise()时立即执行executor,并传入resolve和reject
25     executor(_resolve, _reject)
26   }
27  
28   // then方法,接收一个成功的回调和一个失败的回调,并push进对应队列
29   then(resolveFn, rejectFn) {
30     this._resolveQueue.push(resolveFn)
31     this._rejectQueue.push(rejectFn)
32   }
33 }

 

posted @ 2023-04-29 19:35  TangTaue  阅读(9)  评论(0编辑  收藏  举报