单例模式

简单的单例模式实现

需要用一个变量来标记是否已经创建过一个实例对象

var Single = function(name) {
  this.name = name;
  this.instance = null;
}
Single.getInstance = function(name) {
  if (!this.instance) {
    this.instace = new Single(name)
  }
  return this.instance
}

var a = Single.getInstance("a");
var b = Single.getInstance("b");
a === b //true

缺点:不透明,相比于正常的new创建实例,创建单例必须用getInstance方法.

透明的单例模式

目标是实现一个透明的单例类,下面将使用CreateDiv单例类,在页面创建唯一的div节点

var CreateDiv = (function() {
  var instance = null; //自执行函数保证instance只初始化一次
  function CreateDiv(html) {
    if (instance) {
      return instance
    }
    this.html = html;
    // this.init();
    return instance = this;
  }
  CreateDiv.prototype.init = function() {
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.append(div);
  }

  return CreateDiv
})()
var a = new CreateDiv("a");
var b = new CreateDiv("b");
a === b //true

为了将instance封装起来使用了自执行匿名函数和闭包 增加了复杂性 并且CreateDiv负责了两件事 第一是创建对象和执行init方法 第二是保证只有一个对象 。违背了单一职责原则

用代理实现单例模式

为了解决上述的单一职责问题 下面引入代理模式实现这个单例模式 首先把创建对象和执行init方法分离出来

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

接下来引入代理类

var createDivProx = (function() {
  var instance;
  return function(html) {
    if (!instance) {
      instance = new CreateDiv(html)
    }
    return instance
  }

})()
var a = new createDivProx("a")
var b = new createDivProx("b")
a == b //true

Js的单例模式-使用闭包封装的私有变量

既然我们需要的是一个对象 那就没必要先创建一个类

为了避免全局变量的滥用,可以把变量封装在闭包内部 只暴露接口和外界通信

var user = (function() {
  var _name = "yosgi"
  var _age = 29;
  return {
    getUserInfo() {
      return _name + "-" + _age
    }
  }
})()

或者使用es6的Symbol

var _name = Symbol.for('name');
var _age = Symbol.for('age');
window[_name] = "yosgi"
window[_age] = 29 //_name 和_user不会被无意中覆盖

完成一个惰性单例

惰性单例指在需要的时候才创建实例对象。

下面完成一个需求是 当点击login按钮时被创建的div框(以后也有可能是iframe框)。

var createLoginLayer = (function () {
  var div;
  return function () {
    if (!div) {
      div = document.createElement("div");
      div.style.display = "none";
      document.body.appendChild(div);
    }
    return div
  }
})()
document.getElementById("login").onclick = function () {
  var LoginLayer = createLoginLayer()
  LoginLayer.style.display = 'block';
};

为了解决单一职责的问题,将维护单一实例的代码提取出来

//getSingle用来管理单例
var getSingle = (function() {
  var result;
  return function(fn) {
    if (!result) {
      //闭包使result可以保存fn的结果
      result = fn.apply(this, arguments)
    }
    return result
  }
})()

//createSingleIframe用来创建实例
var createSingleIframe = function() {
  iframe = document.createElement("iframe");
  iframe.style.display = "none";
  document.body.appendChild(iframe);
  return iframe
}

document.getElementById("login").onclick = function() {
  var Iframe = getSingle(createSingleIframe)
  Iframe.style.display = 'block';
  Iframe.src = "http://baidu.com"
};

单例模式的应用

在渲染页面中的一个列表之后,绑定addEventListener click 事件 ,如果是通过ajax动态添加数据,实际上只需要在第一次渲染绑定一次,如果不想判断是第几次渲染 可以利用getSingle

var getSingle = (function() {
  var result;
  return function(fn) {
    if (!result) {
      result = fn.apply(this, arguments)
    }
    return result
  }
})()


var bindEvent = function() {
  document.getElementById("div1").addEventListener("click", () => {
    console.log("click")
  })
  return true
}
var render = function() {
  getSingle(bindEvent)
}
render()
render()
render()

可以注意到,点击div1多次也只会触发一次点击事件(当然也可以使用onclick,一个click在同一时间只能指向唯一的对象 这点我以前没注意)