单例模式

5/25/2019JavaScript单例模式设计模式Vue

#解释

#什么是单例模式?

单例就是保证一个类只有一个实例,实现的方法是判断实例是否存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

在《JavaScript 设计模式与开发实践》中单例模式的定义是:

保证一个类仅有一个实例,并提供一个访问它的全局访问点

#模块作用

  1. 模块间通信
  2. 系统中某个类的对象只能存在一个
  3. 保护自己的属性和方法

#实现一个单例模式

下面例子中instance就是实例变量,getInstance方法是判断是否创建实例变量

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

  constructor(name) {
    this.name = name;
  }

  getName() {
    console.log(this.name);
  }
}

Singleton.instance = null;
const a = Singleton.getInstance("sven1");
const b = Singleton.getInstance("sven2");
console.log(a === b); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#使用闭包的方式实现

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

  getName() {
    console.log(this.name);
  }
}

Singleton.getInstance = (() => {
  let instance = null;
  return name => {
    if (!instance) {
      instance = new Singleton(name);
    }
    return instance;
  };
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#透明的单例模式

在上面的例子中,我们通过Singleton.getInstance()方法来获取单例类,这个类并不直观(不透明性),因此我们使用透明,以下是创建一个 div 节点

const CreateDiv = (() => {
  let instance = null;
  const CreateDiv = function(html) {
    if (instance) return instance;
    this.html = html;
    this.init();
    return (instance = this);
  };

  CreateDiv.prototype.init = function() {
    let div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
  };

  return CreateDiv;
})();

const a = new CreateDiv("sven1");
const b = new CreateDiv("sven2");
console.log(a === b);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#用代理实现单例模式

在上面的例子中会感觉代码有些奇怪,同时代码生涩难懂,下面使用代理类来改善代码

class CreateDiv {
  constructor(html) {
    this.html = html;
    this.init();
  }

  init() {
    let div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
  }
}

const ProxySingletonCreateDiv = (() => {
  let instance = null;
  return function(html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  };
})();

const a = new ProxySingletonCreateDiv("sven1");
const b = new ProxySingletonCreateDiv("sven2");

console.log(a === b); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

#JavaScript 中的“单例模式”

单例模式的核心是确保只有一个实例,并提供全局访问。全局变量不是单例模式,但在 JavaScript 开发中,我们经常会把全局变量当成单例来使用

以下其实是全局变量,但它满足了创建单例模式的条件

let home = {
  style: "Chinese styles",
  door() {
    // ...
  },
  window() {
    //...
  }
};
1
2
3
4
5
6
7
8
9

#惰性单例

{% note primary %}
使用了惰性单例模式:在需要的时候创建单例,再次调用就使用第一次实例化后的实例对象
{% endnote %}

例如:点击登录按钮跳出弹窗就可以使用惰性单例模式

<button id="loginBtn">登录</button>
1
const createLoginLayer = () => {
  let div = document.createElement("div");
  div.innerHTML = "Modal";
  div.style.display = "none";
  document.body.appendChild(div);
  return div;
};

const getSingle = fn => {
  let result;
  return function(...arg) {
    return result || (result = fn(arg));
  };
};

const createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById("loginBtn").addEventListener("click", () => {
  let loginLayer = createSingleLoginLayer();
  loginLayer.style.display = "block";
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#在 Vue 中的使用

#效果

单例模式图片

点击按钮能够新增条目,点击条目能够删除这一行

#目录结构

|- src
|   |- components
|       |- dialog-container
|           |- dialog-container.vue
|           |- index.js
|   |- App.vue
|   |- main.js
1
2
3
4
5
6
7

#具体实现

<!-- dialog-container.vue -->
<template>
  <div>
    <ul>
      <li v-for="list in lists" :key="list.id" @click="del(list.id)">
        {{ list.content }}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    methods: {
      del(id) {
        const index = this.lists.findIndex(ele => ele.id === id);
        if (-1 !== index) {
          this.lists.splice(index, 1);
        }
      }
    }
  };
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

具体单例使用

import Vue from "vue";

import Container from "./dialog-container.vue";

Container.installSlot = (() => {
  let [component, lists] = [null, null];

  return (id, list) => {
    if (typeof list === "object" && typeof id === "string") {
      if (!component) {
        const container = Vue.extend(Container);
        lists = [list];
        component = new container({
          data() {
            return {
              lists
            };
          }
        });

        document.getElementById(id).appendChild(component.$mount().$el);
      } else {
        lists.push(list);
      }
    }
  };
})();

export default {
  install: Vue => {
    Vue.prototype.$dialog = Container.installSlot;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

配置及使用

import Dialog from "./components/dialog-container";
Vue.use(Dialog);
1
2
<template>
  <div id="app">
    <div id="content">
      <button @click="hi('content')">Hellos</button>
    </div>
  </div>
</template>
<script>
  export default {
    name: "App",
    methods: {
      hi(domId) {
        const id = Math.random()
          .toString()
          .substr(2, 6);

        // 使用
        this.$dialog(domId, {
          id,
          content: `The id is ${id}`
        });
      }
    }
  };
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

#总结

单例模式其他具体场景不是很清楚,希望以后能够看到其他用法,对于设计模式我也才刚开始看,继续努力。

#参考文章

Last Updated:5/25/2024, 2:23:06 AM