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

实现单例模式

在 JavaScript 开发中,单例模式应用的非常广泛,比如果说全局的模态框、Vuex 等等都应用的了单例的思想。
单例模式就是无论调用多少次 new,返回的都是同一个实例对象,在 JavaScirt 中我们只要定义一个变量,然后在第一次调用 new 时,将实例赋值给这个变量,后面再次调用 new 时,我们直接返回这个变量就可以了。

利用静态属性

利用静态属性储存类的实例,然后改用 getInstance 获取类实例,实现单例模式:

  1. function User(name) {
  2. this.name = name;
  3. }
  4. User.instance = null;
  5. User.getInstance = (...args) => {
  6. if (User.instance) return User.instance;
  7. return (User.instance = new User(args));
  8. };
  9. const user = User.getInstance('test');
  10. const user1 = User.getInstance('test');
  11. console.log(user === user1); // true

利用闭包实现

instance 变量放到 getInstance 的闭包中实现单例:

  1. function User(name) {
  2. this.name = name;
  3. }
  4. User.getInstance = (() => {
  5. let instance;
  6. return (...args) => {
  7. if (instance) return instance;
  8. return (instance = new User(args));
  9. };
  10. })();
  11. const user2 = User.getInstance('test');
  12. const user3 = User.getInstance('test');
  13. console.log(user2 === user3); // true

透明的单例模式

上面两种方式都将 new 获取实例,改为了调用 getInstance 方法,有没有方法可以 new 来获取单例呢?答案就是 this

利用闭包缓存 instance 变量,将 this 赋值给 instance 实现单例:

  1. const Dog = (() => {
  2. let instance;
  3. // 返回一个函数,这个函数用来创建实例
  4. return function (name) {
  5. if (instance) return instance;
  6. this.name = name;
  7. // 将 this 赋值给 instance
  8. return (instance = this);
  9. };
  10. })();
  11. const dog = new Dog('金毛');
  12. const dog1 = new Dog('金毛');
  13. console.log(dog === dog1); // true

代理模式实现单例

上面实现单例的代码中,我们可以发现其做了两件事:1. 实现单例、2. 创建对应的类实例,在 “单一职责原则” 的概念中不推荐这样实现,应该一个对象或方法应该只做一件事,下面就来改造一下:

  1. const ProxySingleDog = (() => {
  2. let instance;
  3. return function(name) {
  4. if (instance) return instance;
  5. return (instance = new Dog(name));
  6. }
  7. })();
  8. const dog2 = ProxySingleDog('哈士奇');
  9. const dog3 = ProxySingleDog('哈士奇');
  10. console.log(dog2 === dog3); // true
  1. 我们可以将这个代理写的更通用一些,这样后面需要使用单例猫、单例猪的时候就不用重复去写实现单例代码了。
  1. const ProxySingle = (fn) => {
  2. let instance;
  3. return function(...args) {
  4. if (instance) return instance;
  5. return (instance = new fn(...args));
  6. }
  7. };
  8. const SingleCat = ProxySingle(Cat);
  9. console.log(new SingleCat() === new SingleCat()); // true

单例模式的应用

在项目开发中我们经常会通过 Message 组件来给用户一些提示:
1641382530(1).png
这个组件的使用方式常常都是通过 API 调用实现的 this.$message(),这时就需要我们手动去挂载这个组件,当 this.$message() 调用很多次时,没有必要重复的去创建和挂载组件,一直使用同一个即可,这时我们就可以使用单例模式来实现它:

Message 组件:

  1. <template>
  2. <div class="message-list">
  3. <div v-for="item in messageList" :key="item">
  4. {{ item }}
  5. </div>
  6. </div>
  7. </template>
  8. <script>
  9. import { reactive } from 'vue';
  10. export default {
  11. setup() {
  12. const messageList = reactive([]);
  13. const add = (message) => {
  14. messageList.push(message)
  15. };
  16. return {
  17. messageList,
  18. add,
  19. };
  20. },
  21. }
  22. </script>
  23. <style lang="postcss">
  24. .message-list {
  25. position: fixed;
  26. left: 50%;
  27. top: 5%;
  28. transform: translateX(-50%);
  29. }
  30. .message-list > div {
  31. width: 200px;
  32. line-height: 40px;
  33. text-align: center;
  34. border: 1px solid red;
  35. margin-bottom: 10px;
  36. }
  37. </style>

instance.js

  1. import { createApp } from 'vue';
  2. import Message from './index.vue';
  3. let instance;
  4. function message(content) {
  5. if (instance) {
  6. return instance.add(content);
  7. }
  8. const container = document.createElement('div');
  9. instance = createApp(Message).mount(container);
  10. document.body.appendChild(container);
  11. instance.add(content);
  12. }
  13. export default message;

使用 Message:

  1. import Message from './components/message/instance';
  2. Message('Hello World');

整理与 JavaScript 设计模式与开发实践。