用js理解常用设计模式

目录

原则

创建型

单例模式

工厂模式

js闭包:函数工厂

结构型

代理模式

装饰器模式

行为型

职责链模式

观察者模式


原则

  • S – Single Responsibility Principle 单一职责原则
    • 一个程序只做好一件事
    • 如果功能过于复杂就拆分开,每个部分保持独立
    • 例如:Promise每个then中的逻辑只做好一件事
  • O – OpenClosed Principle 开放/封闭原则
    • 对扩展开放,对修改封闭
    • 增加需求时,扩展新代码,而非修改已有代码
    • 例如:Promise如果新增需求,扩展then
  • L – Liskov Substitution Principle 里氏替换原则
    • 子类能覆盖父类
    • 父类能出现的地方子类就能出现
  • I – Interface Segregation Principle 接口隔离原则
    • 保持接口的单一独立
    • 类似单一职责原则,这里更关注接口
  • D – Dependency Inversion Principle 依赖倒转原则
    • 面向接口编程,依赖于抽象而不依赖于具体
    • 使用方只关注接口而不关注具体类的实现
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
        case 'mobile':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        default:
            return true;
    }
}

想添加其他规则就得在函数里面增加 case 。违反了开放-封闭原则(对扩展开放,对修改关闭)

给 API 增加一个扩展的接口:

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        }
    };
    //暴露接口
    return {
        //校验
        check(str, type){
            return rules[type]?rules[type](str):false;
        },
        //添加规则
        addRule(type,fn){
            rules[type]=fn;
        }
    }
})();

//调用方式
//使用mobile校验规则
console.log(checkType.check('188170239','mobile'));
//添加金额校验规则
checkType.addRule('money',function (str) {
    return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金额校验规则
console.log(checkType.check('18.36','money'));

创建型

单例模式

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

class LoginForm {
    constructor() {
        this.state = 'hide'
    }
    show() {
        if (this.state === 'show') {
            alert('已经显示')
            return
        }
        this.state = 'show'
        console.log('登录框显示成功')
    }
    hide() {
        if (this.state === 'hide') {
            alert('已经隐藏')
            return
        }
        this.state = 'hide'
        console.log('登录框隐藏成功')
    }
 }

 LoginForm.getInstance = (function () {
     let instance
     return function () {
        if (!instance) {
            instance = new LoginForm()
        }
        return instance
     }
 })()

let obj1 = LoginForm.getInstance()
obj1.show()

let obj2 = LoginForm.getInstance()
obj2.hide()

console.log(obj1 === obj2)

优点:

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

缺点:

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

应用:登录框

工厂模式

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

该模式使一个类的实例化延迟到了子类

而子类可以重写接口方法以便创建的时候指定自己的对象类型

class Product1 {
    product() {
        console.log("生产一线");
    }
}
class Product2 {
    product() {
        console.log("生产二线");
    }
}
class Factory {
    constructor() {
        this.Product1 = Product1;
        this.Product2 = Product2;
    }
    create(name, callBack) {
        const product = new this[name]();
        product.product();
        return callBack("susess");
    }
}
let p = new Factory();
p.create("Product1", (res) => {
    console.log(res);
});

 不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中

class Car {
  constructor(name, color) {
    this.name = name;
    this.color = color;
  }
}
class Factory {
  static create(type) {
    switch (type) {
      case "car":
        return new Car("汽车", "白色");
        break;
      case "bicycle":
        return new Car("自行车", "黑色");
        break;
      default:
        console.log("没有该类型");
    }
  }
}
let p1 = Factory.create("car");
let p2 = Factory.create("bicycle");
console.log(p1, p1 instanceof Car); // {name: '汽车', color: '白色'} true
console.log(p2, p2 instanceof Car); // {name: '自行车', color: '黑色'} true

js闭包:函数工厂

function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

var add5 = makeAdder(5);// fun(y){5+y}
var add10 = makeAdder(10);// fun(y){10+y}

console.log(add5(2)); // 7
console.log(add10(2)); // 12

优点:

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

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

结构型

  • 装饰者模式: 扩展功能,原有功能不变且可直接使用
  • 代理模式: 显示原有功能,但是经过限制之后的

代理模式

是为一个对象提供一个代用品或占位符,以便控制对它的访问

应用:

  • ES6 的 proxy 
  • HTML元 素事件代理
1. 给"ul"标签添加点击事件
2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
注意:
1. 必须使用DOM0级标准事件(onclick)
 
target表示当前触发事件的元素
currentTarget是绑定处理函数的元素
只有当事件处理函数绑定在自身的时候,target才会和currentTarget一样
 
  • .
  • .
  • .

装饰器模式

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

class Cellphone {
    create() {
        console.log('生成一个手机')
    }
}
class Decorator {
    constructor(cellphone) {
        this.cellphone = cellphone
    }
    create() {
        this.cellphone.create()
        this.createShell(cellphone)
    }
    createShell() {
        console.log('生成手机壳')
    }
}
// 测试代码
let cellphone = new Cellphone()
cellphone.create()

console.log('------------')
let dec = new Decorator(cellphone)
dec.create()

优点:

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

缺点:

  • 多层装饰比较复杂。

应用:

  • ES7 Decorator
  • 比如现在有4 种型号的自行车,我们为每种自行车都定义了一个单 独的类。现在要给每种自行车都装上前灯、尾 灯和铃铛这3 种配件。如果使用继承的方式来给 每种自行车创建子类,则需要 4×3 = 12 个子类。 但是如果把前灯、尾灯、铃铛这些对象动态组 合到自行车上面,则只需要额外增加3 个类

行为型

职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求直到有一个对象处理它为止

// 请假审批,需要组长审批、经理审批、总监审批
class Action {
    constructor(name) {
        this.name = name
        this.nextAction = null
    }
    setNextAction(action) {
        this.nextAction = action
    }
    handle() {
        console.log( `${this.name} 审批`)
        if (this.nextAction != null) {
            this.nextAction.handle()
        }
    }
}

let a1 = new Action("组长")
let a2 = new Action("经理")
let a3 = new Action("总监")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

优点:

  • 简化了对象。使得对象不需要知道链的结构

缺点:

  • 不能保证某个请求一定会被链中的节点处理,这种情况可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。
  • 使程序中多了很多节点对象,可能再一次请求的过程中,大部分的节点并没有起到实质性的作用。他们的作用仅仅是让请求传递下去,从性能当面考虑,要避免过长的职责链到来的性能损耗。

应用:

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

观察者模式

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('result')
    },
    1000);
}) 

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执行依赖

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._resolveQueue = []    // then收集的执行成功的回调队列
    this._rejectQueue = []     // then收集的执行失败的回调队列

    /*由于resolve/reject是在executor内部被调用, 
    因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue*/

    let _resolve = (val) => {
      // 从成功队列里取出回调依次执行
      while(this._resolveQueue.length) {
        const callback = this._resolveQueue.shift()
        callback(val)
      }
    }
    // 实现同resolve
    let _reject = (val) => {
      while(this._rejectQueue.length) {
        const callback = this._rejectQueue.shift()
        callback(val)
      }
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调,并push进对应队列
  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn)
    this._rejectQueue.push(rejectFn)
  }
}

你可能感兴趣的:(前端面试,javascript,设计模式,开发语言)