js设计模式学习之单例模式

单例模式

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

实现一个单例模式

用一个变量标志当前是否已经为某个类型建立过对象,若是是,则下次直接返回以前建立的对象。设计模式

var Singleton = function (name) {
  this.name = name;
  this.instance = null;
}

Singleton.prototype.getName = function () {
  console.log(this.name);
}

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

var a = Singleton.getInstance('Tony1');
var b = Singleton.getInstance('Tony2');

console.log(a === b); // true
复制代码

经过 Singleton.getInstance来获取 Singleton 类的惟一对象,里边使用了 new 来获取,致使了这个类的“不透明性”。闭包

透明的单例模式

建立一个“透明”的单例类,就是让咱们从这个类中建立对象的时候能够和使用其余普通类同样:var aa = new CreateDiv('Sisi1');app

var CreateDiv = (function () {
  var instance;

  var CreateDiv = function (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.appendChild(div);
  };

  return CreateDiv;

})();

var aa = new CreateDiv('Sisi1');
var bb = new CreateDiv('Sisi2');

console.log(aa === bb);  // true
复制代码

下面这段代码中,CreateDiv 的构造函数负责了两件事:建立对象和执行初始化 init 方法,及保证只有一个对象:dom

var CreateDiv = function (html) {
  if (instance) {
    return instance;
  }
  this.html = html;
  this.init();
  return instance = this;
};
复制代码

可是,若是咱们要建立不少的div,这里的 return instance = this; 就须要删掉。函数

用代理实现单例模式

这时候,为了不上面不能复用的尴尬,经过引入代理类的方式,把负责管理单例的逻辑移交至代理类ProxySingletonCreateDiv,这样CreateDiv只是一个普通的类。学习

var CreateDiv = function (html) {
  this.html = html;
  this.init();
};

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

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

var aa = new ProxySingletonCreateDiv('Tony1');
var bb = new ProxySingletonCreateDiv('Tony2');

console.log(aa === bb); // true
复制代码

JavaScript 中的单例模式

单例模式的核心是:确保只有一个实例,并提供全局访问。ui

  1. 使用命名空间

对象字面量的方式:this

var namespace1 = {
  a: function() {
    console.log(1);
  },
  b: function() {
    console.log(2);
  }
}
namespace1.a(); //1
复制代码

把a和b都定义为 namespace1 的属性,减小了变量和全局做用域打交道的机会,还能够动态地建立命名空间:spa

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.log(MyApp);

// 至关于:
var MyApp = {
  event: {},
  dom: {
    style: {}
  }
}
复制代码
  1. 使用闭包封装私有变量

使用下划线约定私有变量 _name 和 _age。

var user = (function () {
  var _name = 'Seven';
  var _age = 27;

  return {
    getUserInfo: function () {
      return _name + '-' + _age;
    }
  }
})();

console.log(user.getUserInfo()) // Seven-27
复制代码

惰性单例

宗旨:在须要的时候才建立对象!!!

栗子:QQ的登陆浮窗

第一种方案:页面加载完成的时候便建立好浮窗。

var loginLayer = (function () {
  var div = document.createElement('div');
  div.innerHTML = '我是一个小小的悬浮框';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
})();

document.getElementById('loginBtn').addEventListener('click', function () {
  loginLayer.style.display = 'block';
});
复制代码

可是,无论咱们登陆与否,都会建立悬浮窗,因此咱们能够修改成:在点击登陆的时候再建立悬浮窗。

var createLoginLayer = function () {
  var div = document.createElement('div');
  div.innerHTML = '我是一个小小的悬浮框';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
};

document.getElementById('loginBtn').addEventListener('click', function () {
  var loginLayer = createLoginLayer();
  loginLayer.style.display = 'block';
});
复制代码

这时候,虽然达到了惰性的目的,却失去了单例的效果,每次点击登陆,都会建立一个新的悬浮窗。

因此咱们须要一个变量来判断是否已经建立过悬浮窗:

var createLoginLayer = (function () {
  var div;
  return function () {
    if (!div) { // 判断是否已建立
      div = document.createElement('div');
      div.innerHTML = '我是一个小小的悬浮框';
      div.style.display = 'none';
      document.body.appendChild(div);
    }
    return div;
  }
})();

document.getElementById('loginBtn').addEventListener('click', function () {
  var loginLayer = createLoginLayer();
  loginLayer.style.display = 'block';
});
复制代码

通用的惰性单例

虽然上面的悬浮框是一个可用的惰性单例,可是仍然违反了单一职责原则,若是咱们要建立其余的标签,就须要把建立悬浮窗的函数复制一份,再修修改改,没法作到复用。

因此,咱们须要把不变的部分隔离出来,进行抽象,不管建立什么标签,都是同样的逻辑:

var obj;
if(!obj) {
  obj = xxx;
}
复制代码

接着,继续:

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

var createLoginLayer = function () {
  var div = document.createElement('div');
  div.innerHTML = '我是一个小小的悬浮框';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
}

var createSingleLoginLayer = getSingle(createLoginLayer);

document.getElementById('loginBtn').addEventListener('click', function () {
  var loginLayer = createSingleLoginLayer();
  loginLayer.style.display = 'block';
});
复制代码

这时,咱们建立其余标签就只须要关系如何建立该标签就能够:

var createIframe = function () {
  var iframe = document.createElement('iframe');
  iframe.src = 'https://baidu.com';
  document.body.appendChild(iframe);
  return iframe;
}

var createSingleIframe = getSingle(createIframe);

document.getElementById('loginBtn2').addEventListener('click', function () {
  createSingleIframe();
});
复制代码

小结

单例模式是一种简单却很是经常使用的模式,特别是惰性单例技术,在合适的时候才建立对象,而且只建立惟一的一个。

建立对象管理单例 的职责被分布在两个不一样的方法中,两个方法组合起来才具备单例模式的威力。

学习资料:

  • 《JavaScript 设计模式与开发实践》第 4 章
相关文章
相关标签/搜索