yliu

时来天地皆同力,运去英雄不自由

单例模式


单例模式是很常见的一种设计模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例实现

根据上面定义很容易想到用闭包来实现

var dom = (function() {
  var d;
  function createdDiv(html) {
    if (d) {
      return d;
    }
    this.html = html;
    this.init();
    return (d = this);
  }
  createdDiv.prototype.init = function() {
    var dom = document.createElement("div");
    dom.innerHTML = this.html;
    document.body.append(dom);
  };
  return createdDiv;
})();
var d1 = new dom("hello wrold");
var d2 = new dom("你好世界");
console.log(d1 === d2); // true

上面通过变量d来判断,没有就生成一个div,有的话就返回d,不过上面代码生成和保证单一组合在一起实际上违反了面向对象的单一职责,下面就用代理的方式来清除这种耦合。

代理

还是通过上面代码为例,我们通过观察发现createdDiv函数负责div的创建和生成,如何保证类的单一,我们可以使用另外一个函数来检测和返回。

var dom = (function() {
  var d;
  function createdDiv(html) {
    this.html = html;
    this.init();
  }
  createdDiv.prototype.init = function() {
    var dom = document.createElement("div");
    dom.innerHTML = this.html;
    document.body.append(dom);
  };
  return function(h) {
    if (!d) {
      d = new createdDiv(h);
    }
    return d;
  };
})();
var d1 = new dom("hello wrold");
var d2 = new dom("你好世界");
console.log(d1 === d2); // true

惰性

以兼容 dom 事件为例,ie 有自己的 dom 事件添加方法,如果我们要写一个兼容的函数可能会这样写

function addEvent(type, el, fn) {
  if (window.addEventListener) {
    el.addEventListener(type, fn, false);
  } else if (window.attachEvent) {
    el.attachEvent("on" + type, fn);
  }
}

不过这里每次运行函数都会判断一次,实际上没有必要,我们可以通过闭包的形式只判断一次

function addEvent(type, el, fn) {
  if (window.addEventListener) {
    return function() {
      el.addEventListener(type, fn, false);
    };
  } else if (window.attachEvent) {
    return function() {
      el.attachEvent("on" + type, fn);
    };
  }
}

惰性的应用很广泛,许多优化也牵扯其中,比如 webpack 对import()可以做到代码分割,减少首屏加载时间。

信任函数

还是通过惰性的例子为例,我们用了一个闭包来实现,不过也可以通过改写这个函数来实现

function addEvent(type, el, fn) {
  if (window.addEventListener) {
    addEvent = function(type, el, fn) {
      el.addEventListener(type, fn, false);
    };
  } else if (window.attachEvent) {
    addEvent = function(type, el, fn) {
      el.attachEvent("on" + type, fn);
    };
  }
}

es6 之后的单例

在 javascript 中单例实际上有些特殊,因为并没有类,而且单例的作用只是为了保证类的单一性,那我们可以直接定义一个全局变量就可以做到了

var a = {};

这里a在全局只会存在一次,不过缺点也很明显,就是会被覆盖和修改以及污染了全局变量,而从 es6 开始引用了模块的机制,它有些特殊,它是运行前加载,而且会将模块的值动态同步,怎么理解这句话呢?

let d = 5;
setTimeout(() => {
  d = 10;
}, 1000);
export { d };
// 引用

import { d } from "./demo.js";
console.log(d);
setTimeout(() => {
  console.log(d);
}, 1000);

上面会输出 5 和 10,所以根据模块这一特性可以很轻松实现单例模式

let event;
if (!event) {
  if (window.addEventListener) {
    event = function(type, el, fn) {
      el.addEventListener(type, fn, false);
    };
  } else if (window.attachEvent) {
    event = function(type, el, fn) {
      el.attachEvent("on" + type, fn);
    };
  }
}
export default event;

最后

上面简单介绍了单例的传统实现,以及在 javascript 中的实现,最后额外提一下,es6 新增的symbol也可以跟单例想结合使用,它确保了这个属性名是唯一的,我们可以通过symbol.for来判断有没有这个symbol