The code has been written for several years, and the design pattern is in a state of forgetting and forgetting. Recently, I have some feelings about the design pattern, so I will learn and summarize it again.

Most speak design pattern articles are using Java, c + + such based on the class the static type of language, as a front-end developer, js this prototype based dynamic language, function has become a first class citizen, slightly different on some design patterns, even simple to don’t like to use the design patterns, sometimes also can produce some confusion.

The following are summarized in the order of “scenario” – “Design Pattern definition” – “code implementation” – “more scenarios” – “total”. If there are any improper points, welcome to discuss.

scenario

If you need to implement a global loading mask layer, the normal display would look like this:

But if the user calls the loaing twice in a row, the second mask will override the first:

This looks buggy, so we need to use a singleton to limit the user to one global loading at a time.

The singleton pattern

Take a look at wikipedia’s definition:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance. This is useful when exactly one object is needed to coordinate actions across the system.

Arguably the simplest design pattern is to ensure that there is only one instance of a class.

Take a look at a Java example:

public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(a) {}

    public static Singleton getInstance(a) {
        returnINSTANCE; }}Copy the code

The constructor is set to private and does not allow external calls, providing getInstance to get the object.

There is also a Lazy initialization mode, which delays the creation of the object until getInstance is called. However, calling getInstance in multiple threads at the same time can cause multiple objects to be created, so locking is also required.

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(a) {}
    public static Singleton getInstance(a) {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = newSingleton(); }}}returninstance; }}Copy the code

The singleton pattern has many controversies, such as its poor testability, unfriendly support for abstraction, inheritance, polymorphism, etc., but I feel that it is mainly caused by languages like class, so I won’t discuss it here.

Going back to JS, let’s simulate it:

const Singleton = function () {
    this.instance = null;
};
Singleton.getInstance = function (name) {
    if (!this.instance) {
        this.instance = new Singleton();
    }
    return this.instance;
};
const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true
Copy the code

But the above is really handan mimics the same Java implementation, in fact, JS create objects do not necessarily need to through the new way, below we discuss in detail.

Js singleton pattern

First of all, the objects generated by the singleton pattern are usually utility objects, such as jQuery. It does not require us to pass arguments through the constructor, so there is no need to new a constructor to generate an object.

We can treat a as a singleton simply by using a literal object, var a = {}.

A typical singleton might look like this, exposing several methods for external use.

var Singleton = {
  method1: function () {
    // ...
  },
  method2: function () {
    // ...}};Copy the code

But if a Singleton has private attributes, it could be written like this:

var Singleton = {
  privateVar: 'I'm a private property'.method1: function () {
    // ...
  },
  method2: function () {
    // ...}};Copy the code

However, the Singleton allows the outside world to modify the value of privateVar at will.

To solve this problem, we use closures to privatize some properties and methods using IIFE (Immediately Invoked Function Expression).

var myInstance = (function() {
  var privateVar = ' ';

  function privateMethod () {
    // ...
  }

  return { 
    method1: function () {},method2: function () {}}; }) ();Copy the code

But with the advent of ES6 and Webpack, we rarely define a module as above, but through a single file, a file is a module, but also can be regarded as a singleton object.

// singleton.js
const somePrivateState = []

function privateMethod () {
  // ...
}

export default {
  method1() {
    // ...
  },
  method2() {
    // ...}}Copy the code

Then use import.

// main.js
import Singleton from './singleton.js'
// ...
Copy the code

Import the same file even if there is another file.

// main2.js
import Singleton from './singleton.js'
Copy the code

However, the Singleton of these two different files is still the same object, which is a feature of the ES Moudule.

What about after Webpack transforms ES6 into ES5, will this approach still be a singleton?

The answer is yes. Look at the Webpack package, which uses IIFE and caches the first import module, and uses the previous cache for the second import. If you look at the implementation of __webpack_require__, the logic is the same as the singleton pattern.

function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  
  // Application of singleton pattern
  if(cachedModule ! = =undefined) {
    return cachedModule.exports;
  }

  var module = (__webpack_module_cache__[moduleId] = {
    exports: {},}); __webpack_modules__[moduleId](module.module.exports,
    __webpack_require__
  );
  return module.exports;
}
Copy the code

Code implementation

It is also easy to solve the problem of global loading mentioned in the beginning. Similarly, if there is an instance of loading, we just need to return directly.

Here’s a quick look at ElementUI’s handling of global loading.

// ~/packages/loading/src/index.js

let fullscreenLoading;

const Loading = (options = {}) = >{...// Options default to fullscreen
  options = merge({}, defaults, options);
  if (options.fullscreen && fullscreenLoading) {
    return fullscreenLoading; // There is a direct return
  }

  let parent = options.body ? document.body : options.target;
  let instance = new LoadingConstructor({
    el: document.createElement('div'),
    data: options }); .if (options.fullscreen) {
    fullscreenLoading = instance;
  }
  return instance;
};
Copy the code

When Element loading is called twice at the same time, there is only one loading mask layer, and the second one is not shown.

mounted() {
  const first = this.$loading({
    text: 'I was the first full screen loading',})const second = this.$loading({
    text: 'I'm the second one'
  })

  console.log(first === second); // true
}
Copy the code

More scenes

If we use ES6 modules, we don’t need to worry about singletons, but what if we use a third-party library that, instead of exporting an instance object, exports a function/class?

For example, the publish-subscribe Event object is definitely a global singleton. If we use the Eventemitter3 node package, take a look at its export:

'use strict';

var has = Object.prototype.hasOwnProperty
  , prefix = '~';

/**
 * Constructor to create a storage for our `EE` objects.
 * An `Events` instance is a plain object whose properties are event names.
 *
 * @constructor
 * @private* /
function Events() {}

//
// We try to not inherit from `Object.prototype`. In some engines creating an
// instance in this way is faster than calling `Object.create(null)` directly.
// If `Object.create(null)` is not supported we prefix the event names with a
// character to make sure that the built-in object properties are not
// overridden or used as an attack vector.
//
if (Object.create) {
  Events.prototype = Object.create(null);

  //
  // This hack is needed because the `__proto__` property is still inherited in
  Are built like Android 4, iPhone 5.1, Opera 11 and Safari 5.
  //
  if (!new Events().__proto__) prefix = false;
}

/**
 * Representation of a single event listener.
 *
 * @param {Function} fn The listener function.
 * @param {*} context The context to invoke the listener with.
 * @param {Boolean} [once=false] Specify if the listener is a one-time listener.
 * @constructor
 * @private* /
function EE(fn, context, once) {
  this.fn = fn;
  this.context = context;
  this.once = once || false;
}

/**
 * Add a listener for a given event.
 *
 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
 * @param {(String|Symbol)} event The event name.
 * @param {Function} fn The listener function.
 * @param {*} context The context to invoke the listener with.
 * @param {Boolean} once Specify if the listener is a one-time listener.
 * @returns {EventEmitter}
 * @private* /
function addListener(emitter, event, fn, context, once) {
  if (typeoffn ! = ='function') {
    throw new TypeError('The listener must be a function');
  }

  var listener = new EE(fn, context || emitter, once)
    , evt = prefix ? prefix + event : event;

  if(! emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;else if(! emitter._events[evt].fn) emitter._events[evt].push(listener);else emitter._events[evt] = [emitter._events[evt], listener];

  return emitter;
}

/**
 * Clear event by name.
 *
 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
 * @param {(String|Symbol)} evt The Event name.
 * @private* /
function clearEvent(emitter, evt) {
  if (--emitter._eventsCount === 0) emitter._events = new Events();
  else delete emitter._events[evt];
}

/**
 * Minimal `EventEmitter` interface that is molded against the Node.js
 * `EventEmitter` interface.
 *
 * @constructor
 * @public* /
function EventEmitter() {
  this._events = new Events();
  this._eventsCount = 0; }.../**
 * Add a listener for a given event.
 *
 * @param {(String|Symbol)} event The event name.
 * @param {Function} fn The listener function.
 * @param {*} [context=this] The context to invoke the listener with.
 * @returns {EventEmitter} `this`.
 * @public* /
EventEmitter.prototype.on = function on(event, fn, context) {
  return addListener(this, event, fn, context, false); }; . .// Alias methods names because people roll like that.
//
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;

//
// Expose the prefix.
//
EventEmitter.prefixed = prefix;

//
// Allow `EventEmitter` to be imported as module namespace.
//
EventEmitter.EventEmitter = EventEmitter;

//
// Expose the module.
//
if ('undefined'! = =typeof module) {
  module.exports = EventEmitter;
}

Copy the code

As you can see, it exports EventEmitter directly. If each page were to import it and generate objects with New EventEmitter(), publishing and subscribing would be a mess because they are not the same object.

At this point, we can create a new module and export an instantiation object, which other pages can use to implement the singleton pattern.

import EventEmitter from 'eventemitter3';
// Globally unique event bus
const event = new EventEmitter();
export default event;
Copy the code

The total

The singleton pattern is simple, mainly to ensure that global objects are unique, but JS is special compared to the singleton pattern that generates objects through class.

Because we can generate objects directly in JS, and the object is globally unique, in JS, the singleton pattern is natural and we are not aware of it.

Especially now the development of ES6 module, each module is also a singleton object, ordinary business development rarely to apply the singleton pattern, in order to cite the above example is really exhausted brain cells, haha.

I don’t know if you have used singleton pattern in your daily development, please tell me, thank you.

More design patterns recommended reading:

Front end design mode series – strategic mode

Front-end design mode series – proxy mode

Front end design mode series – decorator mode

Front end design mode series – Observer mode

Front end design pattern series – publish subscribe pattern