Skip to content
成为赞助商

设计模式

设计模式

  • 作用:针对特定问题的简洁/优雅的解决方案,将变化封装,提升 复用性、可维护性。

  • 注意:

    • 设计模式的滥用,导致不该使用的地方使用,适得其反
    • 将可变的部分封装后,剩下的就是不变和稳定的部分
    • 开发阶段可能会增加代码量,但好的设计模式能明显降低后期维护成本
    • 模式应该用在正确的地方,设计模式是在发展的,不同语言特征下设计模式存在差异
  • 关注模式的意图而不是结构,只有在具体的环境中才有意义

  • 1995 年最初被提出时有 23 种设计模式是针对 cpp、java 等静态类型、传统面向对象编程语言设计,而在 JS 中有些模式可能已 经不再需要,而有些模式的实现会有变化。比如有些人为了实现 JS 版的工厂模式,生硬的将创建对象的步骤延迟到子类中,实际上在静态类 型语言中让子类来“决定”创建对象的原因是为了迎合“依赖倒置”原则,解开对象类型的耦合,让对象表现出多态性,而在 JS 这种动态类型语言中,多态是天生的, JS 不存在类型耦合,不需要将对象延迟到子类中创建,所以 JS 其实是不需要工厂模式的。应用设计模式是为了解决问题,像上述这种牵强的应用只会让人觉得设计模式既难懂又没什么用,影响设计模式在 JS 中的发展。这些年有更多的模式被发现并总结了出来,比如有些 JS 书籍会提到模块模式、沙箱模式,这些模式能否经受住时间考验还有待验证。

行为型模式

行为型设计模式通常用来解耦,它是对在不同的对象之间划分责任算法的抽象化,行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用;行为型模式一共有以下 11 种

  • 模板方法模式(Template Method)
  • 策略模式(Strategy)
  • 命令模式(Command)
  • 中介者模式(Mediator)
  • 观察者模式(Observer)
  • 迭代器模式(Iteratior)
  • 访问者模式(Visiter)
  • 责任链模式(Chain of Responsibility)
  • 备忘录模式(Memento)
  • 状态模式(State)
  • 解释器模式(Interpreter)

策略模式

针对一组算法,将每一个算法封装到具有共同接口的独立的类中,使得它们可以互换。

微信图片_20220908201939

观察者模式

定义:观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式。

  • 理解:一个目标对象对应多个观察者,当目标对象发生改变时,就通知所由的观察者

  • 步骤

    • 在目标对象中维护了一个观察者的数组,新增时将观察者向数组中 push;
    • 然后通过 notify 通知所有的观察者;
    • 每个观察者只有一个 update 函数,用来接收观察者更新后的一个回调;
  • 观察者模式把观察者对象维护在目标对象中的,需要发布消息时直接发消息给观察者。在观察者模式中,目标对象本身是知道观察者存在的

  • js
    // 定义一个目标对象
    class Subject {
      constructor() {
        this.Observers = [];
      }
      add(observer) {
        //添加
        this.Observers.push(observer);
      }
      remove(observer) {
        //移除
        this.Observers.filter((item) => item === observer);
      }
      notify() {
        //通知所有观察者
        this.Observers.forEach((item) => {
          item.update();
        });
      }
    }
    //定义观察者对象
    class Observer {
      constructor(name) {
        this.name = name;
      }
      update() {
        console.log(`my name is:${this.name}`);
      }
    }
    
    let sub = new Subject();
    let obs1 = new Observer("observer11");
    let obs2 = new Observer("observer22");
    sub.add(obs1); // 添加到观察者队列
    sub.add(obs2);
    sub.notify(); // 触发所有的观察者

设计模式

订阅发布模式

发布订阅模式是基于一个事件(主题)通道,希望接收通知的对象Subscriber通过自定义事件订阅主题,被激活事件的对象Publisher通过发布主题事件的方式通知各个订阅该主题的Subscriber对象。

  • 与观察者模式的不同:增加了第三方即事件中心;目标对象状态的改变并非直接通知观察者,而是通过第三方的事件中心来派发通知。
  • 订阅者订阅主题,发布者推送某个主题时,订阅该主题的所有读者都会被通知到;避免了观察者模式无法进行过滤筛选的缺陷。
  • 而发布/订阅模式中,发布者并不维护订阅者,也不知道订阅者的存在,所以也不会直接通知订阅者,而是通知调度中心,由调度中心(公用的对象实例)通知订阅者。
  • 例:
    • Vue2 中的模板语法,实现数据与界面的双向绑定,数据与页面相互更新

单例模式

单例就是保证一个类只有一个实例,并提供一个访问它的全局访问点

  • 实现方法
    • 一般是先判断实例存在与否,如果存在直接返回,
    • 如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
    • 在 JavaScript 里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象
  • 使用场景
    • 团队开发中,可能会产生命名冲突,这时候单例模式就能很好的解决这个问题。
    • 全局模态框,同一时间只允许弹出一个模态框,内容可以不一样,但都是同一个框,elementUI

代理模式

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

  • 常用虚拟代理形式:某一个花销很大的操作,可以使用虚拟代理的方式延迟到需要它的时候再去创建它,(虚拟代理实现图片懒加载)
  • 图片懒加载方式,使用 loading 图占位,然后通过异步的方式加载图片,等图片记载好了再替换掉

中介者模式

  • 通过抽离配置项,进行代码的控制,实现各个事件的解耦,仅仅维护好中介者就行了

装饰器模式

  • 不改变原对象的基础上,在程序运行时给对象动态的添加方法。

JS设计模式

参考《JavaScript 设计模式》2015年8月第一版,作者:张容铭

面向对象编程

避免污染全局

  1. 将多个函数/变量放在一个变量(对象)中保存,减少覆盖或被覆盖的风险

    js
    let Obj1 = {}
    Obj.Fn1 = function(){}
    
    // 对象的另一种形式:js中函数也是对象
    let Obj2 = function(){} 
    Obj2.Fn1 = function(){ ... }
    Obj2.Fn2 = function(){ ... }
    
    // 	为方便复用,通过函数返回值的形式创建,每次调用都是全新的,使用上互不影响
    let Obj3 = function(){
        return {
            Fn1:function(){},
            Fn2:function(){},
        }
    } 
    let a = Obj3();
    a.Fn1();
                          
    // 真正意义上的类,通过new关键字创建
    let Obj4 = function(){
        this.fn1=function(){...}
        this.fn2=function(){...}
    } 
    let a = new Obj4();
    // 这样每次new创建都会产生一个新的对象,但this.创建方法会造成很多开销
    // 优化:利用原型链的继承关系,创建一次供后代持续使用
    Obj4.prototype.fn3=function(){}      
    // 或者
    obj4.prototype={
        fn4:function(){},
        fn5:function(){}
    }
    // 注意:以上两种方法不要混合使用,避免覆盖之前的赋值

链式编程

js
// 链式调用的核心,就是函数在执行完成后,返回了this,即当前对象
let Obj = {
    fn1:function(){
        ... return this
    }
    fn2:function(){
        ... return this
    }
}
obj.fn1().fn2()

// 放到类的原型上时使用,需要先new一下再使用
let Obj2 =function(){}
Obj2.prototype = {
    fn1:function(){
        ... return this
    }
    fn2:function(){
        ... return this
    }
}
let obj2= new Obj2()
obj2.fn1().fn2()

利用原始类型

js
// 对 Function的原型进行操作,就可以让所有的函数都具有某个方法/属性
// 但依旧保持不能污染其他人使用的原则,可以单独创建一个使用
Function.prototype.addMethod=function(name,fn){
	this[name]=fn    
    return this  // 返回this,可以实现链式添加
}

// 创建属于自己的函数
var methods = new Function()
methods.addMethod("checkName",function(){
    ... 
    return this // 每个添加的函数都返回this,可实现方法的链式调用
})

知识卡:

  • 通过this添加的属性方法与prototype添加的属性方法有什么不同?
    • this定义的属性或方法是该对象自身拥有,每次new新的对象都会重新创建
    • prototype继承的属性在创建新的对象时,不会再次创建
  • 概念:
    • 私有属性/方法:由于js函数作用域,声明在函数内部的属性和方法外界访问不到(类.属性 也拿不到)
    • 共有属性/方法:在函数内部通过this创建的属性和方法 或 通过类通过prototype创建的属性/方法,在new创建对象时都会拥有一份并且可以被外界访问
      • 特权方法:通过this创建的方法,可以访问到函数内部的 私有属性和私有方法!
    • 静态共有属性/方法:在函数外部通过点语法创建的属性/方法,在new创建对象时并没有被执行到,new创建的对象也无法获取他们,但能通过类来使用。
    • 静态私有属性/方法:通常借助闭包实现,
    • 构造器:在new创建对象时,调用特权方法初始化实例对象的一些属性,因此也叫类的构造器
    • new关键字:通过new关键字创建对象,实质是对新对象this的不断赋值,并将prototype指向类的prototype所指向的对象。
  • 总结:
    • 静态-new创建的对象中无法通过this访问
    • 私有-在类的外部无法直接访问但可以借助实例的特权方法操控
    • 共有-this或prototype创建的属性/方法,在创建的实例中可以被访问
js
// 1.无静态私有方法/属性时:
var Book = function(id,name,price){
    // 私有属性
    var num = 1
    // 私有方法
    function checkId(){}
    // 公有属性
    this.id = id
    // 特权方法-可以操作私有属性/方法  也是公有方法
    this.getName = function(){}
    this.setName = function(){}
    this.getPrice = function(){}
    this.getPrice = function(){}
    // 其他公有方法
    this.copy = function(){}
    // 构造器-new创建实例时可以初始化对象的一些属性
    this.setName(name)
    this.setPrice(price)
}

// 静态公有属性(new创建的对象无法访问)
Book.isChinese = true
// 静态公有方法(new创建的对象无法访问)
Book.restTime = function(){}
Book.prototype={
    // 公有属性
    isJsBook:false,
    // 公有方法
    display:function(){}
}

// 测试:
var b = new Book(11,"我的心只悲伤七次",66)
console.log(Book.num , Book.isChinese) // undefined  true
console.log(b.num)  // undefined
console.log(b.isJsBook)  // false
console.log(b.id) // 11
console.log(b.isChinese)  // undefined
js
// 2.借助闭包实现,实现静态私有属性/方法
let Book = (function(){
    // 静态私有变量
    var BookNum = 0
    // 静态私有方法
    function checkBook(name){}
    // 创建类
    function _book(newId,newName,newPrice){
        // 私有变量
        var name,price
        // 私有方法
        function checkId(){}
    	// 特权方法-可以操作私有属性/方法  也是公有方法
    	this.getName = function(){}
    	this.setName = function(){}
    	this.getPrice = function(){}
    	this.getPrice = function(){}
        //公有属性
        this.id=newId
        //公有方法
      	this.copy=function(){}
        
        bookNum++
        if(bookNum>100){ ... }
        
        // 构造器
        this.setName(name);
        this.setPrice(price);
	}
	// 构建原型
	_book.prototype={
		isJsBook:false, // 静态公有方法
		display:function(){} // 静态公有属性
	}
	
	return _book;  // 返回类
})()

安全模式-检察长

  • 在使用函数类的时候容易出现 创建实例时 遗忘 new关键字而导致问题
  • 解决方法:使用不同情况下的不同现象,使用判断做区分并手动解决异常下的问题
js
var Book = function(){
    if(this instanceof Book){
        ... // 正常代码
    }else{
        return new Book()
    }
}
    
var book1 = new Book()
var book2 = Book()

创建型设计模式

访客总数 总访问量统计始于2024.10.29