通过烹饪一顿简单的饭菜来解释 JavaScript 中的状态
当你开始编写简单的 JavaScript 程序时,你不需要担心所使用的变量的数量,或者不同的函数和对象如何协同工作。
例如,大多数人一开始会使用大量的全局 变量,或者作用域位于文件顶层的变量。它们不属于任何单独的类、对象或函数。
例如,这是一个名为state的全局变量:
let state = "global";
但是一旦你的程序开始涉及许多不同的功能和/或对象,你将需要为你的代码创建一套更严格的规则。
这就是状态概念发挥作用的地方。状态描述整个程序或单个对象的状态。它可以是文本、数字、布尔值或其他数据类型。
它是协调代码的常用工具。例如,一旦你更新了状态,一堆不同的函数就能立即对这一变化做出反应。
本文在流行的 JavaScript 库React 的背景下描述状态。
但你猜怎么着?一旦你的代码变得复杂,即使是状态也会让你头疼!改变状态可能会导致意想不到的后果。
我们就此打住吧。状态是面向对象编程(OOP)中常用的工具。但是,许多程序员更喜欢函数式编程,这种编程不鼓励状态更改。JavaScript 支持这两种范式。
好吧,一下子说了这么多术语。我想找到一种方法来展示 OOP 和函数式编程如何实现相同的目标,即使函数式编程不使用状态。
本教程将从 OOP 和功能的角度展示如何烹制意大利面和酱汁。
以下是两种不同方法的简要预览:
让我们开始吧。为了理解本教程,您只需要了解 JavaScript 中的函数和对象。
面向对象方法(使用状态)
在上图中,我们展示了制作这顿意大利面晚餐的两种不同方法:
- 一种关注不同工具(如炉子、锅和意大利面)状态的方法。
- 一种注重食物本身进展的方法,没有提及单个工具(锅、炉等)的状态。
面向对象的方法侧重于更新状态,因此我们的代码将具有两个不同级别的状态:
- 整体,或者整个用餐的状态。
- 对于每个对象都是本地的。
本教程将使用 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'; }
注意:我简化了 console.log 语句以专注于状态更新。
以下是该逻辑的直观表示:
前
后
它有两种状态。当 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
哇!一下子就这么多了。事情是这样的。
- 我们创建了“Pasta”的新原型,其中每个对象都有一个名为cookedStatus的本地状态
- 我们创建了一个名为 myMeal 的 Pasta 新实例
- 我们使用了在上一个代码片段中创建的 pastaPot 对象的状态来确定是否应该将 myMeal 中名为 cookedStatus 的状态更新为 cooked。
- 由于 pastaPot 中 boilStatus 的状态为“沸腾”,所以我们的意大利面现在已经煮熟了!
这个过程的直观表现如下:
前
后
所以,我们现在有一个对象的局部状态,它依赖于另一个对象的局部状态。而这个局部状态又依赖于某个全局状态!你可以看出这有多么具有挑战性。但是,至少目前还比较容易理解,因为状态是显式更新的。
函数方法(无状态)
为了完全理解状态,你应该能够找到一种方法,在不实际修改状态的情况下实现与上述代码相同的结果。这就是函数式编程的用武之地!
函数式编程有两个核心价值将其与 OOP 区分开来:不变性和纯函数。
我不会对这些主题进行过多深入探讨,但如果您想了解更多,我鼓励您查看 JavaScript 函数式编程指南。
这两个原则都不鼓励在代码中使用状态修改。这意味着我们不能使用局部或全局状态。
相反,函数式编程鼓励我们将参数传递给各个函数。我们可以使用外部变量,但不能将其用作状态。
下面是煮意大利面的函数示例。
const stoveTemp = 500;
const cookPasta = (temp) => {
if(temp > 400)
return 'cooked';
}
console.log(cookPasta(stoveTemp));
// 'cooked'
这段代码将成功返回字符串“cooked”。但请注意,我们并没有更新任何对象。该函数只是返回下一步将要使用的值。
相反,我们专注于一个函数的输入和输出:cookPasta。
这种视角关注的是食物本身的变化,而不是烹饪工具的变化。虽然视觉化起来有点困难,但我们不需要让函数依赖于外部状态。
它看起来是这样的。
可以将其视为用餐进度的“时间线视图” - 此特定功能仅涵盖第一部分,即从干面食到熟面食的过渡。
现在我们来介绍第二部分,也就是上菜的部分。以下是上菜的代码,它会放在上面的代码块之后。
const serveMeal = (pasta) => {
if (pasta == 'cooked')
return 'Dinner is ready.'
}
console.log( serveMeal(cookPasta(stoveTemp)) );
// 'Dinner is ready.'
现在,我们将 cookPasta 函数的结果直接传递给 serveMeal 函数。同样,我们无需改变状态或数据结构即可完成此操作。
下面是使用“时间线视图”来展示这两个功能如何协同工作的图表。
对更多视觉教程感兴趣吗?
如果您想阅读更多有关 HTML、CSS 和 JavaScript 的可视化教程,请访问CodeAnalogies 主网站,其中有 50 多个教程。
鏂囩珷鏉ユ簮锛�https://dev.to/kbk0125/state-in-javascript-explained-by-cooking-a-simple-meal-4ego