JavaScript 单例模式的应用

发布时间:2024年01月24日

**简而言之,“应用程序中贯穿始终的单一全局实例就是单例的意思。

在整个应用程序中都可以访问的、只能创建一次的类称为单例。应用程序的不同部分都可以使用这个单一实例,这使得单例在管理应用程序中的全局状态时非常有用。

首先,让我们看一下使用 ES2015 类可以创建一个什么样的单例。对于这个例子,我们将构建一个计数器类,它有:

  • 一个getInstance方法,返回实例的值
  • 一个getCount方法,返回counter变量的当前值
  • 一个increment方法,使counter的值加1
  • 一个decrement方法,使counter的值减1
let counter = 0;
 
class Counter {
  getInstance() {
    return this;
  }
 
  getCount() {
    return counter;
  }
 
  increment() {
    return ++counter;
  }
 
  decrement() {
    return --counter;
  }
}

但是,这个类还不符合单例的标准!单例应该只能实例化一次。目前,我们可以创建多个计数器类的实例。

确保只能创建一个计数器类的实例。

一种确保只能创建一个实例的方法是创建一个名为instance的变量。在计数器的构造函数中,当创建新实例时,我们可以将instance设置为对该实例的引用。如果instance变量已经有值,我们可以通过检查来防止新实例化。如果是这种情况,则实例已经存在。这不应该发生:应该抛出错误以通知用户。

let instance;
let counter = 0;
 
class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }
 
  getInstance() {
    return this;
  }
 
  getCount() {
    return counter;
  }
 
  increment() {
    return ++counter;
  }
 
  decrement() {
    return --counter;
  }
}
 
const counter1 = new Counter();
const counter2 = new Counter();
// Error: You can only create one instance!

完美!我们不能再创建多个实例了。

让我们从counter.js文件导出计数器实例。但在此之前,我们也应该冻结该实例。 Object.freeze方法确保使用代码无法修改单例。冻结实例上的属性不能添加或修改,这减少了意外覆盖单例上值的风险。

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;

单例模式的实现

让我们看一个实现了计数器示例的应用程序。我们有以下文件:

  • counter.js:包含计数器类,并将计数器实例导出为默认导出
  • index.js:加载 redButton.jsblueButton.js 模块
  • redButton.js:导入计数器,并将计数器increment方法作为红色按钮的事件侦听器添加,通过调用getCount方法记录counter的当前值
  • blueButton.js:导入计数器,并将计数器increment方法作为蓝色按钮的事件侦听器添加,通过调用getCount方法记录counter的当前值
// index.js 👇
import "./redButton";
import "./blueButton";

console.log("Click on either of the buttons 🚀!");



// counter.js 👇
let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;



// redButton.js 👇
import Counter from "./counter";

const button = document.getElementById("red");
button.addEventListener("click", () => {
  Counter.increment();
  console.log("Counter total: ", Counter.getCount());
});



// blueButton.js 👇
import Counter from "./counter";

const button = document.getElementById("blue");
button.addEventListener("click", () => {
  Counter.increment();
  console.log("Counter total: ", Counter.getCount());
});

blueButton.jsredButton.jscounter.js中导入相同的实例。这个实例在两个文件中都导入为计数器

当我们在redButton.jsblueButton.js中调用increment方法时,计数器实例上的counter属性的值会在两个文件中更新。点击红色按钮还是蓝色按钮都没有关系:相同的值在所有实例之间共享。这就是为什么计数器的值会继续加1的原因,即使我们是在不同的文件中调用该方法。

反面模式

将实例化限制为只有一个实例可以潜在地节省很多内存空间。我们不必为每次新建实例设置内存,只需要为那个在整个应用程序中都引用的实例设置内存。然而,单例实际上被认为是一种反面模式*,在 JavaScript 中可以(或… 应该)避免使用它*。

let count = 0;

const counter = {
  increment() {
    return ++count;
  },
  decrement() {
    return --count;
  }
};

Object.freeze(counter);
export { counter };

React 中的状态管理

  • 管理组件状态是使用 React 开发复杂 Web 应用程序的主要挑战之一。状态是决定组件的行为和渲染方式的数据。例如,按钮组件可能具有指示其是否启用或禁用的状态,表单组件可能具有存储用户输入值的状态。
  • 然而,不是所有的状态仅限于单个组件。有时,我们需要在多个组件之间共享状态,甚至在整个应用程序之间共享。例如,我们可能有一个存储用户认证状态的状态,或者存储用户主题首选项的状态。这称为全局状态,在 React 中管理起来可能比较棘手。
  • 然而,使用单例进行全局状态管理存在一些缺点。首先,单例会引入全局变量,如果处理不当,可能会导致错误和冲突。其次,单例创建了可变状态,这意味着任何组件都可以直接修改状态,而不遵循任何规则或约定。这可能使状态不可预测和难以调试。
  • 因此,在 React 中,我们通常依赖诸如 ReduxReact Context 之类的状态管理工具来实现全局状态,而不是使用单例。尽管它们的全局状态行为可能与单例类似,但这些工具提供了只读状态,而不是单例的可变状态。在使用 Redux 时,只有纯函数 reducer 在组件发送 action 后才能通过 dispatcher 更新状态。
文章来源:https://blog.csdn.net/weixin_42429220/article/details/135789455
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。