语句和表达式有什么不同

前言

JavaScript中的语句和表达式有什么不同之处?

对于这个问题,我似乎知道答案,但当我尝试向别人解释时,我却语塞了。对于这个问题我有一种感觉,但无法清晰的表达出来。

我后来才意识到,这个问题极其重要。它可以说是房屋的承重墙,将有助于支撑大量的JavaScript知识。

对React开发者来说,更为如此。你不得不记住的那些JSX规则,以及总是忘记遵守的那些规则,大部分都是 语句/表达式 双重性的结果。

在这篇文章中,我将分享我对这两者区别的一些感悟,以及我们如何在日常工作中使用这些信息。

表达式

从本质上来说,表达式是产生值的一段JavaScript代码。

下面所有的例子全部都是表达式:

  • 1 → 产生值为1
  • "hello" → 产生值为"hello"
  • 5 * 10 → 产生值为50
  • num > 100 → 产生值为true或者false
  • isHappy ? "🙂" : "🙁" → 产生值为一个emoji
  • [1, 2, 3].pop() → 产生值为3

表达式可以包含其他表达式。举例来说,你觉得下面的JS代码中有多少个表达式?

(5 + 1) * 2

答案是一共有5个表达式。

具体来说,分别是以下5个:

  1. (5 + 1) * 2 ,这段代码本身就是表达式,产生的值为12
  2. (5 + 1) ,由于有括号,这个子表达式首先求值,并解析为6
  3. 5,单个数字本身就是表达式,因为它们产生一个值。这个表达式解析为5
  4. 1,同样的道理,这个表达式解析为1
  5. 2,这个数字形成最后的表达式,它解析为2

语句

一个JavaScript程序是一连串的语句。每条语句都是计算机做某件事的指令。

这里是有关JavaScript中语句的示例:

let hi = 5;
if (hi > 10) {
  // 更多语句
}
throw new Error('报错了');

关于语句和表达式,我是这么认为的:语句是支撑我们程序的刚性结构,而表达式则填充了细节。

语句中通常有表达式的 "插槽"。我们可以把任何我们喜欢的表达式放到这些插槽里。

举例来说,声明一个具有表达式插槽的变量可以这么做:

let hi = /* 表达式 */;

在这个插槽中,我们可以使用任何先前看到过的表达式:

let hi = 1;
let hi = "hello";
let hi = 5 * 10;
let hi = num > 100;
let hi = isHappy ? "🙂" : "🙁";
let hi = [1, 2, 3].pop();

就有效语法而言,表达式是可以互换的。如果一个语句有一个表达式插槽,我们可以把任何表达式放在那里,代码就会运行。并且我们不会得到语法报错。

也就是说,我们可能会遇到其他的问题。比如说,下面的代码在语法层面来说是有效的,但如果我们尝试运行就会让浏览器崩溃,因为它会导致死循环:

while ("hello") {
    // 因为"hello"永不改变,因此循环会一遍又一遍的重复,直到脚本崩溃。
    // 语法上是有效的,但仍是有问题的。
}

便捷技巧

想知道一段JS代码到底是语句还是表达式吗?试着将它打印出来吧!

console.log(/* 这里是JS代码 */);

如果能够运行,该代码就是表达式。如果报错,那就是语句(当然,也有可能是非法JS)。

此外,我们甚至可以看到表达式的结果,因为会将结果打印到浏览器的控制台中。

这样可以凑效是因为任意函数的参数都必须是表达式。表达式会产生一个值,并将该值传递到函数中。语法并不会产生一个值,因此语句不能被用作函数的参数。

即使作为一个有经验的开发者,我也非常依赖console.log。它真的是一个好东西。

表达式作为语句

这是一个表达式:1 + 2 + 3

如果我们创建一个只包括这个表达式的JS文件,会发生什么?让我们试想把下面的内容保存为test.js

1 + 2 + 3

该文件中有多少个语句?0个还是1个?

事情是这样的:表达方式不能单独存在。它们总是语句的一部分。所以在这种情况下,我们有一个看起来像这样的语句:

/* 表达式插槽 */

除了表达式插槽之外,该语句基本上是空的。表达式1 + 2 + 3填充了该插槽,那么语句也就生成了。

换句话说,以下所有行都是有效的语句:

// 语句 1:
let hi = /* 表达式插槽 */;
// 语句 2:
return /* 表达式插槽 */;
// 语句 3:
if (/* 表达式插槽 */) { }
// 语句 4:
/* 表达式插槽 */

通常情况下,某些教程会错误地指出,表达式就是语句,但这并不完全正确。表达式和语句是不同的东西。但是语句有可能在不提供任何额外字符的情况下包裹住表达式。这就好像用透明的保鲜膜包裹住一个三明治。

语句通常以分号结尾,它标志着语句的结束。对某些语句来说分号不是必须的,如if语句、while循环和函数声明。

React中的实践

如果你曾使用过React,你可能知道大括号{}允许我们在JSX中嵌入一些JavaScript,就像这样:

function CountdownClock({ secondsRemaining }) {
  return (
    <div>
      Time left:
      {Math.round(secondsRemaining / 60)} minutes!
    </div>
  );
}

这就是React的神奇之处,它可以让我们拥有JavaScript的全部能力。

但有一个问题 — 我们不能在大括号里面放置任意JavaScript代码。具体来说,我们只能包括表达式,而不能包括语句。大括号本质上是在我们的JSX中创建一个表达式插槽。

如果我们尝试在大括号内嵌入一个语句,比如说if/else语句,我们会得到错误:

function CountdownClock({ secondsRemaining }) {
  return (
    // 🚫 语法报错
    <div>
      {if (secondsRemaining > 0) {
        `${secondsRemaining} seconds left`
      } else {
        "Time expired!"
      }}
    </div>
  );
}

这是因为语句不会产生值,只有表达式才会产生值。如果我们想在JSX中嵌入if/else逻辑,我们需要使用一个三元操作符表达式:

function CountdownClock({ secondsRemaining }) {
  return (
    // ✅ 没问题
    <div>
      {secondsRemaining > 0
        ? `${secondsRemaining} seconds left`
        : "Time expired!"
      }
    </div>
  );
}

这似乎是一个诡异的JSX/React限制,但它实际上是一个JavaScript限制。

我想我们经常责怪React的一些看似武断的规则,比如组件必须返回一个顶层元素。但更多的时候,React只是在警告我们一个关于JavaScript的限制。

理解语句和表达式的区别是非常重要的。我们还需要了解JSX是如何编译成JavaScript的,以及React的调度与渲染周期是如何工作的......但是,这些话题已经超出了本篇文章的范围。

总结

一个JavaScript程序由一连串的语句组成。每个语句都是做某件事的指令,比如说,创建一个变量,运行一个if/else条件语句,或者开始一个循环。

表达式产生一个值,这些值被放入语句的插槽内。表达式始终是语句的一部分,即使该语句是空的。例如,下面的代码在运行一个循环时没有使用for语句,但它仍然包含一个”透明保鲜膜”语句:

data.forEach(item => console.log(item));

这种区别可能需要一段时间才能变得显而易见,希望这篇文章可以帮助到你。

以上就是本文的所有内容,欢迎点赞收藏转发~

原文链接:https://www.joshwcomeau.com/javascript/statements-vs-expressions/

作者:Joshua Comeau

posted @ 2022-07-14 23:19  chuckQu  阅读(727)  评论(0编辑  收藏  举报