语句和表达式有什么不同
前言
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个:
(5 + 1) * 2
,这段代码本身就是表达式,产生的值为12
(5 + 1)
,由于有括号,这个子表达式首先求值,并解析为6
。5
,单个数字本身就是表达式,因为它们产生一个值。这个表达式解析为5
。1
,同样的道理,这个表达式解析为1
。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