JavaScript设计模式(一):单例模式实现与应用

先提出一个问题,为什么要学习设计模式

难道是提出一个代码形容词,是为了让代码看起高大上 or 装逼

先看下设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

我的个人理解就是,设计模式是前人对于解决问题提炼出来的一种思想,其实我们日常代码中就会用到一些设计模式。

像我们平时用的装饰器可以看作是装饰者模式 ,ES6 提供的 proxy 也可以看作是代理模式 ,像 JavaScript 中的自定义事件 + addEventListener 就采用的是发布订阅模式

设计模式是为了让你的代码变得简单而优雅。有很高的可重用性可维护性以及可扩展性

设计模式有很多种,我们今天先来盘一下最常用和经典的设计模式之一:单例模式

1、单例模式定义

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点

其实就是,第一次访问会进行初始化,创建一个实例,后面访问的时候拿到的都是这个实例,不会再重新创建

js 复制代码
class Singleton {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  static getInstance(name) {
    return this.instance || (this.instance = new Singleton(name))
  }
}

const instance1 = Singleton.getInstance('zs')
const instance2 = Singleton.getInstance('lisi')
console.log(instance1 === instance2) // true

在单例类Singleton上定义一个获取实例的getInstance方法,第一次调用getInstance的时候创建一个Singleton实例保存在instance属性上,后面再获取就直接取this.instance

但是这样会出现一个问题,用户如果不调用getInstance,而直接去new Singleton,这样还是会创建多个实例出来,这样不是我们所期望的。

2、优化版

我们定义一个Singleton类,并用一个instance变量来保证在new 多次时,全局Singleton类实例的唯一性。

js 复制代码
let instance = null;
class Singleton {
  constructor(name) {
    this.name = name;
    if (!instance) {
      instance = this;
    }
    return instance;
  }
}

const instance1 = new Singleton('zs')
const instance2 = new Singleton('lisi')
console.log(instance1 === instance2) // true

3、更优雅的实现:使用代理模式实现单例

其实我们可以用ES6的proxy来实现单例,其代码如下:

js 复制代码
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
function singleton(className) {
    let instance = null;
    return new Proxy(className, {
      construct(target, args) {
        if (!instance) {
          instance = Reflect.construct(target, args);
        }
        return instance;
      }
    })
  }
const ProxyPerson = singleton(Person);
const person1 = new ProxyPerson();
const person2 = new ProxyPerson();
console.log(person1 === person2); // true

4、代理模式实现单例的好处

  1. 解耦和单一职责原则 :我们使用了代理之后,等于在我们使用的客户端和实际对象两者中间,加入了一层代理对象,而代理对象相当于一个黑盒子,而对我我们客户端来说,使用的时候不关心他里面的逻辑和实现细节,只关心它给我们提供了哪些功能和接口,调用就完事了, 这样遵循了单一职责原则,提高了代码的可维护性。
  2. 控制访问和延迟加载 :其实也可以说就是安全性,在代理层做校验可以说是再好不过了,可以先把一些错误情况给拦截掉,起到访问控制的效果,然后也可以根据情况,决定时候延迟创建实例,也就是我们说的惰性单例,这样对于CPU密集型的实例来说,同时也能大大提高性能
  3. 扩展性 :我们可以在代理层通过继承或实现相同的接口来扩展实际对象的功能,这样就可以做到不修改实际对象的源码,又增加上了扩展功能。

5、单例模式的应用

假如我们要创建一个全局唯一的弹框,我们很容易先写在创建弹框的代码:

js 复制代码
const div = document.createElement('div')
div.innerHTML = '我是全局唯一的弹框'
div.style.display = 'none'
document.body.appendChild(div)

创建弹框我们有两种思路,我们可以在一开始渲染页面的时候就创建这个弹框,然后通过控制display进行显示隐藏,当然也可以惰性的懒加载,第一次用到的时候再去创建弹框,为了性能考虑,我们当然是选择后者。

html 复制代码
<html>
    <body>
        <button id="btn">显示弹框</button>
    </body>
    <script>
    const getSingle = (fn) => {
        let instance = null
        return (...args) => {
            return instance || (instance = fn.apply(this, args))
        }
    }
    const createModel = () => {
        const div = document.createElement('div')
        div.innerHTML = '我是全局唯一的弹框'
        div.style.display = 'none'
        document.body.appendChild(div)
        return div;
    }
    const createSingleModel = getSingle(createModel);
    btn.addEventListener('click', () => {
        const model = createSingleModel();
        model.style.display = 'block';
    })
    </script>
</html>

我们先把创建弹框的逻辑抽离到createModel方法中,然后我们创建一个管理单例的方法getSingle,先调用getSingle,将createModel交给getSingle去管理,这样就实现了唯一性,然后在按钮点击的时候,在调用getSingle返回的方法就行啦。

小结

上面介绍Javascript最经典的设计模式之一单例模式,简单来说,单例就是单实例,就是全局只能被创建一次,我们还用了代理对象来实现单例,用以增加其维护性扩展性

相关推荐
Dxy12393102162 小时前
JS如何把数据添加到列表中
前端·javascript·vue.js
御形封灵2 小时前
基于canvas的路网编辑交互
开发语言·javascript·交互
m0_502724952 小时前
Arco design vue 阻止弹窗关闭
javascript·vue.js·arco design
蜡台2 小时前
Uniapp 实现 二手车价格评估 功能
前端·javascript·uni-app·估值·汽车抵押·二手车评估
砍光二叉树2 小时前
【设计模式】创建型-原型模式
设计模式·原型模式
Yan-英杰2 小时前
TypeScript+React 全栈生态实战:从架构选型到工程落地,告别开发踩坑
javascript·学习·typescript
海天鹰2 小时前
JSZip库读取ePub电子书目录
javascript
比特森林探险记2 小时前
Element Plus 实战指南
前端·javascript
FlyWIHTSKY2 小时前
Vue3 单文件中不同的组件
前端·javascript·vue.js