单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现单例模式
在 JavaScript 开发中,单例模式应用的非常广泛,比如果说全局的模态框、Vuex 等等都应用的了单例的思想。
单例模式就是无论调用多少次 new,返回的都是同一个实例对象,在 JavaScirt 中我们只要定义一个变量,然后在第一次调用 new 时,将实例赋值给这个变量,后面再次调用 new 时,我们直接返回这个变量就可以了。
利用静态属性
利用静态属性储存类的实例,然后改用 getInstance 获取类实例,实现单例模式:
function User(name) {this.name = name;}User.instance = null;User.getInstance = (...args) => {if (User.instance) return User.instance;return (User.instance = new User(args));};const user = User.getInstance('test');const user1 = User.getInstance('test');console.log(user === user1); // true
利用闭包实现
将 instance 变量放到 getInstance 的闭包中实现单例:
function User(name) {this.name = name;}User.getInstance = (() => {let instance;return (...args) => {if (instance) return instance;return (instance = new User(args));};})();const user2 = User.getInstance('test');const user3 = User.getInstance('test');console.log(user2 === user3); // true
透明的单例模式
上面两种方式都将 new 获取实例,改为了调用 getInstance 方法,有没有方法可以 new 来获取单例呢?答案就是 this。
利用闭包缓存 instance 变量,将 this 赋值给 instance 实现单例:
const Dog = (() => {let instance;// 返回一个函数,这个函数用来创建实例return function (name) {if (instance) return instance;this.name = name;// 将 this 赋值给 instancereturn (instance = this);};})();const dog = new Dog('金毛');const dog1 = new Dog('金毛');console.log(dog === dog1); // true
代理模式实现单例
上面实现单例的代码中,我们可以发现其做了两件事:1. 实现单例、2. 创建对应的类实例,在 “单一职责原则” 的概念中不推荐这样实现,应该一个对象或方法应该只做一件事,下面就来改造一下:
const ProxySingleDog = (() => {let instance;return function(name) {if (instance) return instance;return (instance = new Dog(name));}})();const dog2 = ProxySingleDog('哈士奇');const dog3 = ProxySingleDog('哈士奇');console.log(dog2 === dog3); // true
我们可以将这个代理写的更通用一些,这样后面需要使用单例猫、单例猪的时候就不用重复去写实现单例代码了。
const ProxySingle = (fn) => {let instance;return function(...args) {if (instance) return instance;return (instance = new fn(...args));}};const SingleCat = ProxySingle(Cat);console.log(new SingleCat() === new SingleCat()); // true
单例模式的应用
在项目开发中我们经常会通过 Message 组件来给用户一些提示:
这个组件的使用方式常常都是通过 API 调用实现的 this.$message(),这时就需要我们手动去挂载这个组件,当 this.$message() 调用很多次时,没有必要重复的去创建和挂载组件,一直使用同一个即可,这时我们就可以使用单例模式来实现它:
Message 组件:
<template><div class="message-list"><div v-for="item in messageList" :key="item">{{ item }}</div></div></template><script>import { reactive } from 'vue';export default {setup() {const messageList = reactive([]);const add = (message) => {messageList.push(message)};return {messageList,add,};},}</script><style lang="postcss">.message-list {position: fixed;left: 50%;top: 5%;transform: translateX(-50%);}.message-list > div {width: 200px;line-height: 40px;text-align: center;border: 1px solid red;margin-bottom: 10px;}</style>
instance.js
import { createApp } from 'vue';import Message from './index.vue';let instance;function message(content) {if (instance) {return instance.add(content);}const container = document.createElement('div');instance = createApp(Message).mount(container);document.body.appendChild(container);instance.add(content);}export default message;
使用 Message:
import Message from './components/message/instance';Message('Hello World');
整理与 JavaScript 设计模式与开发实践。
