JavaScript设计模式: 单例和模块

英文原文: https://wanago.io/2019/11/11/javascript-design-patterns-1-singleton-and-the-module/

虽然拥有疯狂天才的感觉可能很诱人,但是重新发明轮子通常不是设计软件的最佳方法。很有可能有人已经有了和你一样的问题,并以一种聪明的方式解决了它。这种最佳实践形式化后称为设计模式。今天我们将深入研究它们的概念,并考察一个单例和一个模块。设计模式是什么

设计模式是什么

我们可以将设计模式视为许多开发人员在各种实际情况中测试过的经过验证的解决方案。它们的目标是支持软件设计师以可读和可预测的方式解决常见问题。如果我们将应用程序建立在经过验证的模式的基础上,我们就可以较少地担心整个结构,因为它们往往鼓励我们以一种有组织的方式编写代码。

查看合并了一个设计模式的已经存在的代码库可能比试图理解一个不熟悉的方法更容易。他们也是其他开发商和我们之间的桥梁。使用众所周知的策略可以使交流更快更容易。

设计模式不能作为精确的解决方案。他们为我们提供了一个可以适应我们自己需要的方案。模式没有绑定到特定问题的事实使得它们非常可重用。它们与特定的编程语言无关,但是JavaScript的设计模式比其他的更受欢迎。

你可能已经用过其中的一些了。常见的JavaScript解决方案在实现时往往具有足够的设计模式。React通常合并高阶组件模式和通量架构。Angular应用程序在实现观察者设计模式时似乎工作得很好。

单例

我们从一个称为单例的设计模式开始。它是最著名的模式之一,因此它是一个很好的起点。在其核心中,它限制一个类只有一个实例,并确保它是全局可访问的。当您需要管理整个应用程序中的某些内容时,它可能会派上用场。

The term singleton comes from math and means a set with exactly one element

按照设计,单例对象创建一个类的实例(如果它还不存在的话)。否则,它们将返回对现有实例的引用。

class Singleton {
  static instance;
  constructor() {
    // your logic here
  }
  static getInstance() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = new Singleton();
    return Singleton.instance;
  }
}

现在,每次我们调用Singleton.getInstance(),我们都会得到相同的对象。

Singleton.getInstance() === Singleton.getInstance(); // true

上面的代码乍一看还不错,但是有一些问题。没有什么限制我们直接调用单例构造函数。这时打字稿可能会派上用场。

class Singleton {
  private static instance?: Singleton;
  private constructor() {
    // your logic here
  }
  static getInstance() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = new Singleton();
    return Singleton.instance;
  }
}

通过将单例构造函数设为私有,我们只能在getInstance函数中调用它。
我们可以采用的另一种方法是从构造函数中直接返回实例。

class Singleton {
  static instance;
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;

    // your logic here
  }
}
new Singleton() === new Singleton() // true

上面的代码使它变得不那么透明,因为有些人可能不知道构造函数每次都返回相同的对象。

单例模式与全局变量有很多共同之处。这也是为什么它们通常不被鼓励的原因,因为它们具有相同的缺点,因为它们可能会使应用程序的可读性降低。不管我们认为单例模式是好是坏,它们都是基本的设计模式之一。理解它们也许有一天会派上用场。即使您不决定自己编写它们,也可能在某些应用程序中遇到它们。

模块的模式

在JavaScript应用程序中发现的另一个典型模式是模块设计模式。将应用程序的代码分成模块对于保持代码库的整洁有重要作用。

一段时间以前,一种流行的方法是将一段代码封装在立即调用的函数表达式(IIFE)中。这是因为所有JavaScript文件共享相同的作用域。

index.html

hello.js

function hello() {
  console.log('Hello world!');
}

hello(); // Hello world!

main.js

hello(); // Hello world!

在一个文件中定义某些东西会污染整个全局范围,这不是一个理想的情况。解决此问题的常见方法是通过创建一个函数并立即调用它来引入模块模式。

hello.js

(function(){
  function hello() {
    console.log('Hello world!');
  }

  hello(); // Hello world!
})();

main.js

hello(); // Uncaught ReferenceError: hello is not defined

关于上述方法的一件重要的事情是,如果我们在上面的模块中定义了任何变量,那么它在模块外部是不可用的。

我们还可以通过从立即调用的函数表达式返回一些内容来导出hello函数。

hello.js

const helloModule = (function(){
  function hello() {
    console.log('Hello world!');
  }

  return {
    hello
  }
})();

main.js

helloModule.hello(); // Hello world!

随着JavaScript语言的发展,我们找到了处理上述问题的其他方法。其中一个是ES6模块,每个模块都有自己的文件。现代浏览器已经支持它们了。您也可以在Webpack中使用它们。

Node.js环境还通过实现一个名为CommonJS的模块系统来提供解决方案。让我们来看看一段奇怪的代码:

console.log('Hello');
return;
console.log('world!');

这看起来很奇怪,因为return语句不能出现在函数之外。当我们导入这样一个文件时,Node.js会把它封装在这样一个函数中:

function (exports, require, module, __filename, __dirname) {
  console.log('Hello');
  return;
  console.log('world!');
}

由于上面的原因,模块有自己的作用域,上面的代码运行没有错误。

总结

在本文中,我们了解了什么是设计模式。我们首先要知道的是单例和模块。上述文章的另一个要点是,您可能已经在使用设计模式。这可能是一个好主意,去学习更多的知识,扩大我们的编程词汇表。正因为如此,我们才能更快地找到解决问题的办法。此外,它可能会更有效率和可读性。

英文原文: https://wanago.io/2019/11/11/javascript-design-patterns-1-singleton-and-the-module/

李, 国轩