Javascript中的函数如何“延迟求值”?当想要避免急于评价

How do functions in Javascript "delay evaluation" when wanting to avoid eager evaluation?

本文关键字:于评价 评价 函数 延迟求值 延迟 Javascript      更新时间:2023-09-26

这个例子是从书中摘取的,Javascript Allonge。本课题涉及控制流运算符和函数参数的求值。

const or = (a, b) => a || b
const and = (a, b) => a && b
const even = (n) =>
  or(n === 0, and(n !== 1, even(n - 2)))
even(42)
  //=> Maximum call stack size exceeded.

书中指出这将导致无限递归。我相信我理解这部分。因为所有参数都将被求值,即使or()中的a参数为真,b参数仍将被求值(即在even()下调用or()函数时)。和()也是如此。偶数(n -2)参数最终将被反复求值,n为2,0,-2,-4…

作为解决方案,它说可以将匿名函数作为参数传递。

const or = (a, b) => a() || b()
const and = (a, b) => a() && b()
const even = (n) =>
  or(() => n === 0, () => and(() => n !== 1, () => even(n - 2)))
even(7)
  //=> false

现在我明白了代码是如何被重写来处理包含原始表达式的匿名函数的,但我不明白这是如何"延迟求值"的。因为匿名函数仍然是或()甚至()函数的参数,所以如何阻止它们求值并得到与前面代码相同的结果呢?

因为匿名函数仍然是or()和even()函数的参数,那么如何防止它们计算并达到与前面代码相同的结果呢?

or()/and()调用的参数确实被求值。但这意味着函数表达式被求值(到一个函数),然后传递给or/and,而不是实际调用函数。它只在函数内部的a()/b()中被调用,并且只在实际需要时(因为操作符的短路)才被调用——这才是递归调用的实际位置。

顺便说一下,这个概念也被称为坦克

even()的两个版本中,必须在调用or()之前计算对or()的外部调用的参数。因此,在第一个版本中:

const even = (n) =>
  or(n === 0, and(n !== 1, even(n - 2)))

必须对形参表达式求值,第二个实参将触发无限递归。

然而,在第二个版本中,or()and()函数期望被传递的函数返回值,而不是值本身。因此,在代码进入or()and()的实现之前,这些函数不会被调用。因为or()and()函数是为了利用JavaScript的短路逻辑运算符而编写的,所以没有无限递归。 因此

:

const even = (n) =>
  or(() => n === 0, () => and(() => n !== 1, () => even(n - 2)))

仍然需要计算or()的实际参数,但参数只是函数—它们说明了如何获取一个值,但是在调用之前它们实际上什么都不做。

操作符||&&使用短路求值:

短路评估、最小评估或麦卡锡评估表示编程中某些布尔运算符的语义只执行或求值第二个参数的语言如果第一个参数不足以确定的值表达

例如,如果有a() || b(),调用a返回true,则不会调用b函数。

然而,对于orand函数,您无法实现此行为,因为它们是函数,而不是操作符。传递给函数的形参在调用函数之前求值。

因此,or(a(), b())将同时调用ab,即使前者返回true

传递函数更有效,因为函数在调用之前不会运行。因此,不是比较它们返回的值,而是将函数本身传递给orand,由于它们是使用操作符||&&实现的,因此函数的调用将被短路计算。