1.1.6 条件表达式和谓词
至此,我们能描述的函数类的表达能力还非常有限,因为还没办法先做检测,然后根据检测结果执行不同操作。例如,我们还无法声明一个函数,使它能算出一个数的绝对值。完成此事需要先检查这个数是否非负,然后按下面的规则对不同情况采用不同的动作:
这种结构称为分情况分析,在JavaScript里可以用条件表达式写出来:
这个条件表达式可以用自然语言读为“如果x大于等于0就返回x,否则返回-x。”条件表达式的一般形式是:
条件表达式以predicate开头(下面有时称为“谓词部分”“谓词表达式”,或直接说“谓词”),这是一个值为真或假的表达式。真和假是JavaScript里的两个布尔值,对基本布尔表达式true和false求值将直接得到布尔值真和假。在谓词之后是一个问号,然后是consequent-expression(下面称为“后继部分”或“后继表达式”)、一个冒号和alternative-expression(下面称为“替代部分”或“替代表达式”)。
求值条件表达式时,解释器首先求值表达式中的predicate,如果它的值是真,那么就去求值consequent-expression,并以其值作为这个条件表达式的值。如果predicate的值是假,就去求值alternative-expression,并以其值作为条件表达式的值[14]。
术语谓词(predicate)指返回真或假的运算符或函数,以及求值结果为真或假的表达式。绝对值函数abs里使用了基本谓词>=,这是一个运算符,它以两个数作为参数,检查第一个数是否大于或等于第二个数,并据此分别返回真或假。
如果我们愿意把0作为另一种情况处理,也可以要求这个函数按另一种方式计算数值的绝对值:
在JavaScript里,我们经常用到在一个条件表达式的alternative-expression部分嵌套另一个条件表达式的语句形式,用于描述包含多种不同情况的分情况分析:
在嵌套的表达式x===0?0:-x周围不需要加括号,这是因为条件表达式是右结合的。为了提高可读性,这里加入了一些空格和换行,把分情况分析里的各个?和:都与第一个谓词对齐。解释器会忽略这些空格和换行。分情况分析的一般形式是:
我们把谓词pi和随后的ei一起称为一个子句,这样,分情况分析结构就可以看作子句的序列,最后跟着一个表示其他情况的表达式。按条件表达式的求值规则,求值这种结构时,解释器首先求值谓词p1,如果其值是假就求值p2,如果其值还是假就求值p3。这一过程持续到求值某个谓词得到真,这时解释器就求值该子句的e,并将其值作为整个分情况分析的值。如果所有的p求出值都不是真,这个分情况分析的值就是最后那个其他情况表达式的值。
除了可以应用于数值的基本谓词如>=、>、<、<=、===和!==之外[15],还有几个逻辑复合运算符,利用它们可以构造出各种复合谓词。最常用的三个复合运算符是:
●expression1&&expression2
这个运算描述逻辑合取,其意义大致相当于自然语言的“并且”。这一语法形式实际上是expression1? expression2:false的语法包装(加了语法糖衣[16])。
●expression1||expression2
这个运算描述逻辑析取,其意义大致相当于自然语言的“或者”。这一语法形式实际上是expression1? true:expression2的语法包装。
●!expression
这个运算描述逻辑否定,其意义大致相当于自然语言的“非”。如果expression求出的值是假,整个表达式的值就是真;如果expression的值为真,整个表达式的值就是假。
注意,&&和||都只是语法形式而不是运算符,它们右边的子表达式不一定求值。另一方面,!是一个运算符,遵循1.1.3节说明的求值规则。这是一个一元运算符,也就是说它只有一个运算对象,而至今我们讨论的算术运算符和基本函数都是二元的,都要求两个参数。运算符!应该放在其运算对象前面,因此称为前缀运算符。另一个前缀运算符是算术的求负运算符,前面abs函数里的表达式-x就是使用它的例子。
作为使用这些谓词的例子,数x的值位于区间5<x<10中的条件可以表述为:
语法形式&&的优先级低于比较运算符>和<。另一方面,条件表达式语法形式…?…:…的优先级低于我们遇到过的所有运算符。前面的abs函数里已经用到这个性质。
作为另一个例子,我们可以声明一个谓词,它检测某个数是否大于或等于另一个:
或者也可以写成:
把函数greater_or_equal应用于两个数,其行为就像运算符>=。一元运算符的优先级高于二元运算符,因此,后一例子里的括号是必需的。
练习1.1 下面是一系列语句。对这里的每个语句,解释器将输出什么结果?假定这一语句序列按我们展示的顺序求值。
在最后两个例子里,围绕条件表达式的括号都是必需的,因为条件表达式语法形式的优先级低于运算符+和*。
练习1.2 请把下面这个表达式翻译为JavaScript表达式:
练习1.3 请声明一个函数,它以三个数为参数,返回其中较大的两个数的平方和。
练习1.4 应该注意,我们的求值模型允许函数应用中的函数表达式又是复合表达式,请根据这一认识说明函数a_plus_abs_b的行为:
练习1.5 Ben Bitdiddle发明了一种检测方法,能用于确定解释器究竟采用哪种求值序,是用应用序求值,还是用正则序求值。他声明了下面两个函数:
而后他求值下面的语句:
如果解释器采用应用序求值,Ben会看到什么样的情况?如果解释器采用正则序求值,他又会看到什么情况?请解释你的回答。(在这里我们假设:无论解释器实际使用的是正则序还是应用序,条件表达式的求值规则总是一样的,其中的谓词部分先行求值,再根据其结果确定随后求值的子表达式部分。)