从ES5 到 TypeScript单例模式

更新日期: 2019-05-25阅读: 2.1k标签: 模式

单例模式

单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型 (Creational) 模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。


关键点

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

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。


UML

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。



ES5

面向对象

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。代码如下:

var Singleton = function(name) {
  this.name = name
}
Singleton.prototype.getName = function() {
  alert(this.name)
}
Singleton.getInstance = (function() {
  var instance = null
  return function(name) {
    if (!instance) {
      instance = new Singleton(name)
    }
    return instance
  }
})()

var a = Singleton.getInstance( 'sven1' ); 
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true

我们通过 Singleton.getInstance 来获取 Singleton 类的唯一对象,这种方式相对简单,但有 一个问题,就是增加了这个类的“不透明性”,Singleton 类的使用者必须知道这是一个单例类, 跟以往通过 new XXX 的方式来获取对象不同,这里偏要使 Singleton.getInstance 来获取对象。

上面单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从 “类” 中创建而来。在以类为中心的语言中,这是很自然的做法。比如在 Java 中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的。


class-free

但 JavaScript 其实是一门无类(class-free)语言,也正因为如此,生搬单例模式的概念并无意义。在 JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什 么要为它先创建一个“类” 呢?这无异于穿棉衣洗澡,传统的单例模式实现在 JavaScript 中并 不适用。

1.使用命名空间

适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。 最简单的方法依然是用对象字面量的方式:

var namespace1 = {
  a: function() {
    alert(1)
  },
  b: function() {
    alert(2)
  },
}

2.使用闭包封装私有变量

这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:

var namespace = {
  getSingleton: (function() {
    // BEGIN iife
    var singleton
    return function() {
      if (!singleton) {
        singleton = {
          amethod: function() {
            console.log('amethod')
          },
        }
      }
      return singleton
    }
  })(), // END iife
}
// Invoke: namespace.getSingleton().amethod()


ES6

ES6 里有了模块和类的概念,实现起来会变得不一样:

const singleton = Symbol();
const singletonEnforcer = Symbol();

class SingletonEnforcer {
  constructor(enforcer) {
    if (enforcer !== singletonEnforcer) {
      throw new Error('Cannot construct singleton');
    }

    this._type = 'SingletonEnforcer';
  }

  static get instance() {
    if (!this[singleton]) {
      this[singleton] = new SingletonEnforcer(singletonEnforcer);
    }

    return this[singleton];
  }

  singletonMethod() {
    return 'singletonMethod';
  }

  static staticMethod() {
    return 'staticMethod';
  }

  get type() {
    return this._type;
  }

  set type(value) {
    this._type = value;
  }
}

export default SingletonEnforcer;


Typescript

TypeScript 中有了 private等概念,实现起来会更加有趣。

让我们想象一下,我们想要一个跟踪温度的类。在这个系统中,我们希望有一个入口,可以改变温度。这就是 Singleton类 的样子:

class Singleton {
  private static instance: Singleton;
  private _temperature: number;
  private constructor() { }
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
      Singleton.instance._temperature = 0;
    }
    return Singleton.instance;
  }
  get temperature(): number {
    return this._temperature;
  }
  set temperature(score) {
    this._temperature = score;
  }
  increaseTemperature(): number {
    return this._temperature += 1;
  }
  decreaseTemperature(): number {
    return this._temperature -= 1;
  }
}

const myInstance = Singleton.getInstance();
console.log(myInstance.temperature); // 0

上面的实现有一下几点需要注意:

  • 构造函数 constructor 前面使用了 private 修饰:这意味着我们将无法使用 new 关键字实例化该类。
  • 我们首先检查我们是否有一个类的实例 instance,如果没有,我们将创建并返回实例本身。

如果使用 new 关键字创建对象:

const myInstance = new Singleton(); // Constructor of class 'Singleton' is private and only accessible within the class declaration.

根据上面的例子,我们也可以访问 temperature 属性。现在让我们设置温度值并将其增加/减少几次:

console.log(myInstance.temperature = 25); // 25
console.log(myInstance.increaseTemperature()); // 26
console.log(myInstance.increaseTemperature()); // 27
console.log(myInstance.decreaseTemperature()); // 26


小结

在 java 中,单例模式根据是否 lazy loading (懒汉模式/饿汉模式)以及是否线程安全,分为很多种实现方式。而在 JS 中,我们不用关注线程安全的问题,因此无论是代码复杂度还是实现难度都会低很多。


参考


链接: https://www.fly63.com/article/detial/3885

js设计模式之单例模式,javascript如何将一个对象设计成单例

单例模式是我们开发中一个非常典型的设计模式,js单例模式要保证全局只生成唯一实例,提供一个单一的访问入口,单例的对象不同于静态类,我们可以延迟单例对象的初始化,通常这种情况发生在我们需要等待加载创建单例的依赖。

前端设计模式:从js原始模式开始,去理解Js工厂模式和构造函数模式

工厂模式下的对象我们不能识别它的类型,由于typeof返回的都是object类型,不知道它是那个对象的实例。另外每次造人时都要创建一个独立的person的对象,会造成代码臃肿的情况。

JavaScript设计模式_js实现建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象

html和xhtml,DOCTYPE和DTD,标准模式和兼容模式

主要涉及知识点: HTML与XHTML,HTML与XHTML的区别,DOCTYPE与DTD的概念,DTD的分类以及DOCTYPE的声明方式,标准模式(Standard Mode)和兼容模式(Quircks Mode),标准模式(Standard Mode)和兼容模式(Quircks Mode)的区别

前端四种设计模式_JS常见的4种模式

JavaScript中常见的四种设计模式:工厂模式、单例模式、沙箱模式、发布者订阅模式

javascript 策略模式_理解js中的策略模式

javascript 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句,策略模式提供了开放-封闭原则,使代码更容易理解和扩展, 策略模式中的代码可以复用。

javascript观察者模式_深入理解js中的观察者模式

javascript观察者模式又叫发布订阅模式,观察者模式的好处:js观察者模式支持简单的广播通信,自动通知所有已经订阅过的对象。存在一种动态关联,增加了灵活性。目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

Vue中如何使用方法、计算属性或观察者

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的

我最喜欢的 JavaScript 设计模式

我觉得聊一下我爱用的 JavaScript 设计模式应该很有意思。我是一步一步才定下来的,经过一段时间从各种来源吸收和适应直到达到一个能提供我所需的灵活性的模式。让我给你看看概览,然后再来看它是怎么形成的

Flutter 设计模式 - 简单工厂

在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章地成为了比较典型的创建型模式

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!