通过烹饪一顿简单的饭菜来解释 JavaScript 中的状态

2025-06-09

通过烹饪一顿简单的饭菜来解释 JavaScript 中的状态

当你开始编写简单的 JavaScript 程序时,你不需要担心所使用的变量的数量,或者不同的函数和对象如何协同工作。

例如,大多数人一开始会使用大量的全局 变量,或者作用域位于文件顶层的变量。它们不属于任何单独的类、对象或函数。

例如,这是一个名为state的全局变量:

let state = "global";
Enter fullscreen mode Exit fullscreen mode

但是一旦你的程序开始涉及许多不同的功能和/或对象,你将需要为你的代码创建一套更严格的规则。

这就是状态概念发挥作用的地方。状态描述整个程序或单个对象的状态。它可以是文本、数字、布尔值或其他数据类型。

它是协调代码的常用工具。例如,一旦你更新了状态,一堆不同的函数就能立即对这一变化做出反应。

本文在流行的 JavaScript 库React 的背景下描述状态。

但你猜怎么着?一旦你的代码变得复杂,即使是状态也会让你头疼!改变状态可能会导致意想不到的后果。

我们就此打住吧。状态是面向对象编程(OOP)中常用的工具。但是,许多程序员更喜欢函数式编程,这种编程不鼓励状态更改。JavaScript 支持这两种范式。

好吧,一下子说了这么多术语。我想找到一种方法来展示 OOP 和函数式编程如何实现相同的目标,即使函数式编程不使用状态

本教程将从 OOP 和功能的角度展示如何烹制意大利面和酱汁。

以下是两种不同方法的简要预览:

让我们开始吧。为了理解本教程,您只需要了解 JavaScript 中的函数和对象。

面向对象方法(使用状态)

在上图中,我们展示了制作这顿意大利面晚餐的两种不同方法:

  1. 一种关注不同工具(如炉子、锅和意大利面)状态的方法。
  2. 一种注重食物本身进展的方法,没有提及单个工具(锅、炉等)的状态。

面向对象的方法侧重于更新状态,因此我们的代码将具有两个不同级别的状态:

  1. 整体,或者整个用餐的状态。
  2. 对于每个对象都是本地的。

本教程将使用 ES6 语法来创建对象。以下是全局状态和“Pot”原型的示例。

let stoveTemp = 500;

function Pot(){
  this.boilStatus = '';
  this.startBoiling = function(){
    if( stoveTemp > 400)
      this.boilStatus = "boiling";
  }
}

let pastaPot = new Pot();
pastaPot.startBoiling();

console.log(pastaPot);
// Pot { boilStatus = 'boiling'; }
Enter fullscreen mode Exit fullscreen mode

注意:我简化了 console.log 语句以专注于状态更新。

以下是该逻辑的直观表示:

初始沸腾状态.jpg

煮沸后意大利面食.jpg

它有两种状态。当 pastaPot 通过 Pot 原型创建时,它的 boilStatus 初始为空。但随后,状态发生了变化

我们运行 pastaPot.startBoiling(),现在 boilStatus (本地状态)为“boiling”,因为stoveTemp 的全局状态超过 400。

现在让我们更进一步。我们将根据 pastaPot 的状态让意大利面煮熟。

以下是我们将添加到上述代码片段的代码:

function Pasta (){
  this.cookedStatus = false;
  this.addToPot = function (boilStatus){
    if(boilStatus == "boiling")
      this.cookedStatus = true;
  }
}

let myMeal = new Pasta();
myMeal.addToPot(pastaPot.boilStatus);

console.log(myMeal.cookedStatus);
// true
Enter fullscreen mode Exit fullscreen mode

哇!一下子就这么多了。事情是这样的。

  1. 我们创建了“Pasta”的新原型,其中每个对象都有一个名为cookedStatus的本地状态
  2. 我们创建了一个名为 myMeal 的 Pasta 新实例
  3. 我们使用了在上一个代码片段中创建的 pastaPot 对象的状态来确定是否应该将 myMeal 中名为 cookedStatus 的状态更新为 cooked。
  4. 由于 pastaPot 中 boilStatus 的状态为“沸腾”,所以我们的意大利面现在已经煮熟了!

这个过程的直观表现如下:

pastapotstate1.jpg

pastapotstate2.jpg

所以,我们现在有一个对象的局部状态,它依赖于另一个对象的局部状态。而这个局部状态又依赖于某个全局状态!你可以看出这有多么具有挑战性。但是,至少目前还比较容易理解,因为状态是显式更新的。

函数方法(无状态)

为了完全理解状态,你应该能够找到一种方法,在不实际修改状态的情况下实现与上述代码相同的结果。这就是函数式编程的用武之地!

函数式编程有两个核心价值将其与 OOP 区分开来:不变性和纯函数。

我不会对这些主题进行过多深入探讨,但如果您想了解更多,我鼓励您查看 JavaScript 函数式编程指南

这两个原则都不鼓励在代码中使用状态修改。这意味着我们不能使用局部或全局状态。

相反,函数式编程鼓励我们将参数传递给各个函数。我们可以使用外部变量,但不能将其用作状态。

下面是煮意大利面的函数示例。

const stoveTemp = 500;

const cookPasta = (temp) => {
  if(temp > 400)
    return 'cooked';
}

console.log(cookPasta(stoveTemp));
// 'cooked'
Enter fullscreen mode Exit fullscreen mode

这段代码将成功返回字符串“cooked”。但请注意,我们并没有更新任何对象。该函数只是返回下一步将要使用的值。

相反,我们专注于一个函数的输入和输出:cookPasta。

这种视角关注的是食物本身的变化,而不是烹饪工具的变化。虽然视觉化起来有点困难,但我们不需要让函数依赖于外部状态。

它看起来是这样的。

可以将其视为用餐进度的“时间线视图” - 此特定功能仅涵盖第一部分,即从干面食到熟面食的过渡。

现在我们来介绍第二部分,也就是上菜的部分。以下是上菜的代码,它会放在上面的代码块之后。

const serveMeal = (pasta) => {
 if (pasta == 'cooked')
   return 'Dinner is ready.'
}

console.log( serveMeal(cookPasta(stoveTemp)) );
// 'Dinner is ready.'
Enter fullscreen mode Exit fullscreen mode

现在,我们将 cookPasta 函数的结果直接传递给 serveMeal 函数。同样,我们无需改变状态或数据结构即可完成此操作。

下面是使用“时间线视图”来展示这两个功能如何协同工作的图表。

对更多视觉教程感兴趣吗?

如果您想阅读更多有关 HTML、CSS 和 JavaScript 的可视化教程,请访问CodeAnalogies 主网站,其中有 50 多个教程。

鏂囩珷鏉ユ簮锛�https://dev.to/kbk0125/state-in-javascript-explained-by-cooking-a-simple-meal-4ego
PREV
通过经营一家小型酿酒厂来解释 Web 服务器
NEXT
通过节食解释 JavaScript 的 Reduce 方法