单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现单例模式
在 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 赋值给 instance
return (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 设计模式与开发实践。