这几个js常见的“设计模式”你都了解吗

199 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

设计模式,不是一种语法规范,而是前辈们在开发实践中的经验总结,是用于解决特定问题的某一套“模板”。使用设计模式,可提高代码的可扩展性和可维护性,一定程度上也能提高代码的健壮性。也许我们在平时的开发中,没有刻意使用设计模式,但是根据自己开发的经验和追求代码的质量,也会使用设计模式的思想,只不过没注意这就是设计模式。本文就简单介绍一下JavaScript常用的设计模式,注意在实现方式上,JavaScript和typescript是有所不同的。

前言

经典设计模式有23个,但是是基于后端写的,前端常用的没有23个。设计模式重在设计,也就是在实际开发代码前,应先想好要怎么写,而不是直接上手就写。例如怎么设计数据结构、如何合理拆分组件、如何封装通用API等。在探讨设计模式之前,需要明白设计模式的总原则:开闭原则,即扩展开放,修改关闭。对于面向对象来说,还有一个重要的里氏替换原则。设计模式在Java中体现的更为明显,本文列举前端常用的设计模式:工厂模式、单例模式、代理模式、观察者模式、发布订阅模式、装饰器模式。

工厂模式

一般我们在新建一个对象的实例时,都是使用new的方式创建。使用工厂模式,就是将创建实例的过程封装成统一的函数,并对外提供。在使用时,就不用关心内部实现,只需要调用函数即可。

工厂模式.png

举个栗子

// JavaScript也是支持面向对象的
class Person {}
function factory() {
    return new Person();
}
const student = factory(); // 通过工厂函数实例化对象

使用场景

封装SDK、API

单例模式

全局唯一的实例,无法创建第二个实例,也就是单例。实现单例模式一般有两种方式:类和闭包。为什么类和闭包可以实现单例模式,先来看看什么是单例。顾名思义就是每次创建返回的都是同一个对象, 那么肯定就不能直接使用new的方式创建,而是需要提供一个函数,并在此函数执行时判断是否已经创建了实例,如果创建了就直接返回。类的静态属性是伴随类自身的,闭包是长驻内存的,因此这两种方式都可以实现单例模式。

类实现

class Single {
    constructor() {}; // js中没有私有构造函数,区别于ts
    static getInstance() { // 静态函数
        if (!Single.instance) {
            Single.instance = new Single(); // 第一次Single.instance为undefined,会实例化一个对象
        }
        return Single.instance; // 返回对象实例
    }
}
let s1 = Single.getInstance(); // 不能通过new直接创建,只能通过静态方法getInstance获取
let s2 = Single.getInstance();
console.log(s1 === s2); // true

单例模式-类.png

闭包实现

let Person = (function () {
    let instance; // 定义变量
    let PersonClass = function() {}; // 实例化对象也可借助函数,不一定非得用class
    return function() {
        if (!instance) {
            instance = new PersonClass();
        }
        return instance;
    }
})(); // 自执行函数
let p1 = new Person();
let p2 = new Person();
console.log(p1 === p2);

对比Java

  • JavaScript无法私有化构造函数,Java需要指定私有构造函数
  • JavaScript是单线程,创建单例不用考虑多线程;Java需要考虑多线程
  • JavaScript无法私有静态属性

使用场景

代理模式

使用者无法直接访问对象,需要先访问一个中间代理层。然后在代理层,完成getter、setter等操作。ES6中新增的Proxy就是典型的代理模式

举个栗子

let Person = {
    name: 'hello'
}
let proxy = new Proxy(Person, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return undefined;
        }
    }
});
proxy.name; // hello
proxy.age; // undefined

使用场景

Vue3的响应式监听

观察者模式

观察一个主题,当主题发生变化时,观察者就执行对象的函数。常见的就是使用addEventListener监听事件变化。

window.addEventListener('click', () => {});
// 观察者:window
// 观察主题:click
// 主题发生变化时,触发函数

观察者模式.png

使用场景

使用addEventListener监听事件,需注意在使用完毕后要及时清理监听事件,避免内存泄漏

发布订阅模式

参考EventBus,发布者发布一个事件,已经订阅该事件的订阅者将收到此事件,并做出对应的处理。手写EventBus就不在本处涉及了,只是简单说明发布订阅模式的场景:

// 订阅
event.on('event', () => {});
// 发布
event.emit('event');

观察者模式.png

使用场景

Vue中的Bus事件总线,自定义EventBus,使用完自定义事件后要及时清理,避免内存泄漏

装饰器模式

装饰器是一种与类相关的语法,可用于修改或注释类和类方法,原功能保持不变,在此基础上新增一些功能。该模式还未正式确定,因此在使用时需要在jsconfig中配置experimentalDecorators:true。参考装饰器 - ECMAScript 6入门 (ruanyifeng.com)

装饰类

@testDecorator
class myDecorator {
    // 类
}
function testDecorator(target) {
    target.test = true;
}
console.log(myDecorator.test); // true

@testDecorator就是一个装饰器,修改了myDecorator类的属性,@testDecorator的参数就是myDecorator类自身。

装饰类方法

class Person {
  @nonenumerable
  get kidCount() { return this.children.length; }
}
function nonenumerable(target, name, descriptor) {
  descriptor.enumerable = false;
  return descriptor;
}

使用场景

使用装饰器的例子:nest.jsMixin

总结

  • 本文重点描述了前端,特别是JavaScript中的几种常用设计模式
  • 装饰器还处于“提案”阶段,使用时需要配置experimentalDecorators:true
  • 日常开发中要善于利用设计模式,先设计好,才能更好的开发

原创不易,转载请注明出处