引入

看完这章之后手写了一个谬误的例子:

let SingleTip = function(content) {  this.content = content;  this.showContent = function() {    console.log(this.content);  };  this.instance = null;  this.getInstance = function(content) {    if (this.instance) {      return this.instance;    }    this.instance = new SingleTip(content);    return this.instance;  };}// 报错:SingleTip.getInstance is not a function// let tip1 = SingleTip.getInstance('plz sit down');// let tip2 = SingleTip.getInstance('plz stand up');let singleTip = new SingleTip();let tip1 = singleTip.getInstance('plz sit down');let tip2 = singleTip.getInstance('plz stand up');console.log(tip1 === tip2); // 测试是否为同一个实例

这里的本意是假想某个网页内只容许存在一个tip。当我应用SingleTip.getInstance函数时,报错其并非函数。错就错在getInstance这样的定义形式不对,只能实例化之后能力应用这个函数,这不扯呢?

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

所以正确姿态应该是通过类名SingleTip来取得单例,而不是通过对象名singleTip来取得。

单例模式的简略示例

基于下面的例子,批改getInstance的定义形式:

let SingleTip = function(content) {  this.content = content;  this.showContent = function() {    console.log(this.content);  };  this.instance = null; // 原书示例,此处仿佛无必要}SingleTip.getInstance = function(content) {  if (this.instance) {    return this.instance;  }  this.instance = new SingleTip(content);  return this.instance;}let tip1 = SingleTip.getInstance('plz sit down');let tip2 = SingleTip.getInstance('plz stand up');console.log(tip1 === tip2); // true

这里示例代码未实现content批改。不论调用SingleTip.getInstance时传参如何,获取的实例都是首次调用时获取的实例,且content始终为'plz sit down'。
留神到SingleTip.getInstance的函数体,外面的this指向的是SingleTip(构造函数,自身也是一个对象),最终SingleTip的属性就包含getInstance和instance两个。相比之下,“this.instance = null;”这个instance属性会放到tip1(对象实例),反而并无必要。

咱们还能够把SingleTip.getInstance批改为立刻执行函数的模式,并且令其返回后果是一个函数,把instance放在函数闭包中:(有啥益处没看进去)

let SingleTip = function(content) {  this.content = content;  this.showContent = function() {    console.log(this.content);  };}SingleTip.getInstance = (function(content){  let instance;  return function(content) {    if (instance) {      return instance;    }    instance = new SingleTip(content);    return instance;  }})();let tip1 = SingleTip.getInstance('plz sit down');let tip2 = SingleTip.getInstance('plz stand up');console.log(tip1 === tip2);

更“通明”的单例实现

下面的两个示例代码,存在的问题是依赖getInstance来获取实例,谓之“不够通明”。能不能像一般类一样,通过new关键字来获取实例,而“单例”的限定在类定义外面解决呢?
立刻执行函数就相当于一段绝对独立的代码空间,外面能够做绝对独立的解决,最初返回一个外界须要的货色。这里就是返回了一个_SingleTip,被SingleTip包裹,用SingleTip类名来获取_SingleTip类的一个实例:

let SingleTip = (function(){  let _instance;  let _SingleTip = function(content) {    if (_instance) {      return _instance;    }    this.init(content);    _instance = this;    return _instance;  };  // 报错:this.init is not a function  // 留神_SingleTip.init还没定义,不能提前调用,只能把init放在对应的原型上定义  // _SingleTip.init = function(content) {}  _SingleTip.prototype.init = function(content) {    this.content = content;    this.showContent = function() {      console.log(this.content);    };  }  return _SingleTip;})();let tip1 = new SingleTip('plz sit down');let tip2 = new SingleTip('plz stand up');console.log(tip1 === tip2);

更重要的是,这里模式上用相似一般类一样的new SingleTip()就能够获取单例,SingleTip外部再做“单例”限定,更“通明”了。

代理模式实现单例

从模式上来看,上一个示例中好不容易用立刻执行函数将getInstance作用“合”到SingleTip中,用new SingleTip()即可获取单例。当初,咱们从新做“拆分”,目标是拆分下面示例中立刻执行函数外部的两个步骤:创建对象、治理单例。

let Tip = (function(){  let _Tip = function(content) {    this.init(content);  };  _Tip.prototype.init = function(content) {    this.content = content;    this.showContent = function() {      console.log(this.content);    };  }  return _Tip;})();let SingleTipProxy = (function(){  let _instance;    return function(content){    if (_instance) {      return _instance;    }    _instance = new Tip(content);    return _instance;  }})();let tip1 = new SingleTipProxy('plz sit down');let tip2 = new SingleTipProxy('plz stand up');console.log(tip1 === tip2);

如此拆分,创建对象的代码在Tip中,治理单例的代码在SingleTipProxy中。Tip局部能够复用、创立一般对象;如需创立单例,则联合SingleTipProxy应用。

JavaScript中的单例模式

JavaScript中的全局对象,人造就便于当成单例应用。因为:

单例模式的外围是确保只有一个实例,并提供全局拜访。

咱们须要进一步做限度,避免全局对象被笼罩定义、命名抵触,从而确保全局只有一个实例。通常有如下做法:

  1. 应用命名空间
    JavaScript的命名空间不是什么简单的概念,应用对象字面量即可创立简略的命名空间:
let namespace1 = {  name: 'lucy',  doSomething: function() {    console.log('doSomething');  }};let namespace2 = {  name: 'nancy',  doSomething: function() {    console.log('DO SOMETHING');  }};

书中提到了一种动态创建命名空间的做法:

var MyApp = {};MyApp.namespace = function (name) {  var parts = name.split('.');  var current = MyApp;  for (var i in parts) {    if (!current[parts[i]]) {      current[parts[i]] = {};    }    current = current[parts[i]];  }};MyApp.namespace('event');MyApp.namespace('dom.style');console.dir(MyApp);// 上述代码等价于:var MyAppMyApp = {  event: {},  dom: {    style: {}  }}; 
  1. 应用闭包封装变量
    把变量(name)再封装一层,裸露接口(getName,闭包援用、返回变量)供外界获取。
let namespace1 = (function(){  let _name = 'lucy';  return {    getName: function() {      return _name;    },    doSomething: function() {      console.log('do something');    },  }})();let namespace2 = (function(){  let _name = 'nancy';  return {    getName: function() {      return _name;    },    doSomething: function() {      console.log('DO SOMETHING');    },  }})();