Skip to content
文档章节

单例模式

单例模式(Singleton Pattern)在应用程序运行期间,单例模式只会在全局作用域下创建一次实例对象,让所有需要调用的地方都共享这一单例对象。比如点击某个按钮出现一个弹窗。

其原理无非是创建实例时检查是否已经存在了当前类型实例对象,存在则返回,否则创建一个新的实例对象。

实现

1. 透明版单例模式

通过 new 的方式来获取单例。使用闭包保存 instance 实例。

下面的例子模拟了在一个系统中只能有一个 管理员的情况

js
const UserAdmin = (function () {
  let instance;

  return function UserAdmin(name) {
    if (!instance) {
      this.name = name;
      instance = this;
    }
    return instance;
  };
})();

UserAdmin.prototype.setName = function (name) {
  this.name = name;
};

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

var user1 = new UserAdmin('liuzunkun');
var user2 = new UserAdmin('liuzunkun2');

// user1 === user2
console.log(user1, user2, user1 === user2);

console.log(user1.getName()); // liuzunkun
console.log(user2.getName()); // liuzunkun

user2.setName('liuzunkun3');

console.log(user1.getName()); // liuzunkun3
console.log(user2.getName()); // liuzunkun3

2. 静态方法 getInstance

此处的变化时在方法上假如 getInstance 保存 instance

js
function UserAdmin(name) {
  this.name = name;
}

UserAdmin.getInstance = function (name) {
  if (!UserAdmin.instance) {
    UserAdmin.instance = new UserAdmin(name);
  }

  return UserAdmin.instance;
};

var user1 = UserAdmin.getInstance('liuzunkun');
var user2 = UserAdmin.getInstance('liuzunkun2');

3. class 类中实现

相应的在 class 类中是添加静态方法 getInstance

js
class UserAdmin {
  constructor(name) {
    if (UserAdmin.instance) return UserAdmin.instance;

    this.name = name;
    UserAdmin.instance = this;
  }

  getName(name) {
    return this.name;
  }

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

var user1 = new UserAdmin('liuzunkun');
var user2 = new UserAdmin('liuzunkun2');

4. 代理单例模式

js
function UserAdmin(name) {
  this.name = name;
}

UserAdmin.prototype.setName = function (name) {
  this.name = name;
};

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

var SingletonProxy = function (fn) {
  let instance;
  return function (...args) {
    if (instance) return instance;

    instance = new fn(...args);
    return instance;
  };
};

var UserAdminProxy = SingletonProxy(UserAdmin);

var user1 = new UserAdminProxy('liuzunkun');
var user2 = new UserAdminProxy('liuzunkun2');

惰性单例

假设我们有如下写法, 我们有针对 createLoginLayer 的单例写法,如果我们又有 createIframeLayer 呢,需要复制同样一份代码,这样程序不简洁,因此提取获取单例的公用方法。

js
var createLoginLayer = function () {
  var div;

  return function () {
    if (!div) {
      div = document.createElement('div');
      div.innerHTML = 'LOGIN LAYER';
      div.style.display = 'none';

      document.body.appendChild(div);
    }

    return div;
  };
};

document.getElementById('loginbtn').onclick = function () {
  var loginLayer = createLoginLayer();

  loginLayer.style.display = 'block';
};

下面的写法,将单例获取和核心操作分开处理。

js
var getSingle = function (fn) {
  let result;

  return function () {
    return result || (result = fn.apply(this, arguments));
  };
};

创建登录弹窗

js
var createLoginLayer = function () {
  var div = document.createElement('div');
  div.innerHTML = 'LOGIN LAYER';
  div.style.display = 'none';

  document.body.appendChild(div);
  return div;
};

var createLoginLayerSingleton = getSingle(createLoginLayer);

document.getElementById('loginbtn').onclick = function () {
  var loginLayer = createLoginLayerSingleton();

  loginLayer.style.display = 'block';
};

此时 getSingle 可以被其他方法复用获取单例了。

参考

  1. JavaScript 设计模式与开发实践
  2. JavaScript 设计模式(一):单例模式|掘金