一起来学Javascript 设计模式 之 单例模式

359 阅读3分钟

单例模式

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

单例模式也成为了单体模式,规定一个类只有一个实例,并且提供可全局访问点。 JavaScript 中没有类的定义,单例模式的特点是”唯一“和”全局访问“,那么我们可以联想到 JavaScript 中的全局对象,利用 ES6 的 let 不允许重复声明的特性,刚好符合这两个特点;是的,全局对象是最简单的单例模式;

let obj = {
  name: 'rudy',
  getName: function () {
    return this.name;
  },
};

上述代码中可以知道 obj 就是一个单例,因为 obj 刚好就符合单例模式的两大特点:"唯一"和"可全局访问";

但是我们并不建议这么实现单例,因为全局对象/全局变量会有一些弊端:

  1. 污染命名空间(容易变量名冲突)
  2. 维护时不容易管控 (搞不好就直接覆盖了)

简单版单例模式:

分析:只能有一个实例,所以我们需要使用if分支来判断,如果已经存在就直接返回,如果不存在就新建一个实例;


const Singleton = function (name) {
  this.name = name
  this.instance = null
}


Singleton.prototype.getName = function () {
  return this.name
}

Singleton.getInstance = function (name) {
  if (this.instance) {
    return this.instance
  }

  return this.instance = new Singleton(name)
}

let winner = Singleton.getInstance('winner') // winner
let sunner = Singleton.getInstance('sunner') // sunner

上面代码中我们是通过一个变量 instance 的值来进行判断是否已存在实例,如果存在就直接返回 this.instance,如果不存在,就新建实例并赋值给 instance;

  但是上面的代码还是存在问题,因为创建对象的操作和判断实例的操作耦合在一起,并不符合”单一职责原则“;

改良版: 思路:通过一个闭包,来实现判断实例的操作


let CreateSingleton = (function () {
  let instance = null
  return function (name) {
    this.name = name
    if (instance) {
      return instance
    }

    return instance = this
  }
})()

CreateSingleton.prototype.getName = function () {
  return this.name
}

let winner = new CreateSingleton('winner')
winner.getName() // winner
let sunner = new CreateSingleton('sunner')
sunner.getName() // sunner

代理版单例模式:

 通过代理的形式,将创建对象的操作和实例判断的操作进行解耦拆分,实现更小粒度的划分,符合”单一职责原则“;


let ProxyCreateSingleton = (function () {
  let instance = null
  return function (name) {
    if (instance) return instance
    return instance = new Singleton(name)
  }
})()

let Singleton = function (name) {
  this.name = name
}

Singleton.prototype.getName = function (name) {
  return this.name
}

let winner = new ProxyCreateSingleton("winner");
console.log(winner.getName());
let sunner = new ProxyCreateSingleton("sunner");
console.log(sunner.getName());

惰性单例模式  我们经常会有这样的场景:页面多次调用都有弹窗提示,只是提示内容不一样;  这个时候我们可以立马想到是单例模式,弹窗就是单例实例,提示内容是参数传递;我们可以用惰性单例模式来实现它;


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>
<body>
  <div id="loginBtn">this is rudy</div>
</body>
<script>
  let getSingleton = function (fn) {
    var result;
    return function () {
      return result || (result = fn.apply(this, arguments)); // 确定this上下文并传递参数
    }
  }
  let createAlertMessage = function (html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
  }
  let createSingleAlertMessage = getSingleton(createAlertMessage);
  document.getElementById('loginBtn').onclick = function () {
    let alertMessage = createSingleAlertMessage('<a href="https://www.baidu.com/" style="text-decoration:none;" target="_blank">baidu.com</a>');
    alertMessage.style.display = 'block';
  }
</script>
</html>

 惰性单例是指的是页面开始加载的时候我们的实例是没有进行创建的,是当我们点击页面的div之后才开始创建实例(按需创建),这可以提高我们的网页性能,加快我们的页面渲染速度;

ES6类的写法


class Singleton {
  constructor (name) {
    this.name = name
  }

  static getInstance(name) {
    if (this.instance) return this.instance
    return this.instance = new Singleton(name)
  }
}

let single1 = Singleton.getInstance('rudy')
let single2 = Singleton.getInstance('kobe')

single1 === single2 // true

接下来看看单例模式在Vue中的应用

// Vue3
import { createVNode, render } from 'vue';
import type {App} from "vue";
import MessageConstructor from './index.vue'
export default {
  install(app: App): void {
    // 非单例模式
    // app.config.globalProperties.$message = function(options: any){
    //     const container = document.createElement('div')
    //     const vm = createVNode(
    //       MessageConstructor,
    //       options as any,
    //     )
    //     render(vm, container)
    //     document.body.appendChild(container);
    // }

    // 闭包实现单例模式
    app.config.globalProperties.$message = (function(options) {
      let vm: any;
      return function(options: any) {
        const nodeList = document.querySelectorAll("div.my-prop");
        if (!nodeList.length) {
          vm = null;
        }
        if (!vm) {
          const container = document.createElement('div')
          container.className='my-prop';
          vm = createVNode(
            MessageConstructor,
            options as any,
          )
          render(vm, container)
          document.body.appendChild(container);
        }
        return vm;
      }
    })();
  }
}