“return”关键字属于哪种类型?

92

在JavaScript函数中,我们可选地使用return语句。它是一个关键字。但是return本身的实际类型是什么?实际上,我被下面的例子弄糊涂了:

function add(a, b) {
  return (
    console.log(a + b),
    console.log(arguments)
  );
}

add(2, 2);

输出:

4
[2, 2]

那么,我们可以将逗号分隔的表达式传递到return语句中。这是一个函数吗?

从这里开始,我们能否猜测JavaScript中的每个关键字最终都是一个函数?

我写了一篇小博客作为对这次讨论的总结,你可能想在这里查看它。


106
就在你以为自己已经理解了 JavaScript 之际,却出现了这样的东西... - F.P
12
括号并不意味着你在调用函数,例如:var a = (2 + 2) - alexmac
12
一个更恰当的例子是 var a = (1, 2) -- 这会导致 a 的值为 2,因为逗号运算符的操作方式(是的,逗号像 +| 等数学运算符一样,在λ演算中,逗号运算符实现了 K 组合子)。 - slebetman
6
正确的做法是揭穿误解,而不是点踩。 - el.pescado - нет войне
5
并不是,这是任何C语言衍生语言的标准行为。 - djechlin
显示剩余13条评论
6个回答

128

但是'return'本身的实际类型是什么呢。

'return'没有类型,它不是一个值。

尝试使用typeof return;会得到Unexpected token return

那么,我们可以将逗号分隔的表达式传递到return语句中。这是一个函数吗?

不是,虽然括号可以用于调用函数,但这里它们是包含了几个由逗号运算符分隔的表达式的分组运算符

一个更有用的示例是:

function add(a, b) {
  return (
    (a + b),
    (a - b)
  );
}

console.log(add(2, 2));

执行结果为0,因为a + b的结果被忽略了(它在逗号运算符的LHS上),而返回的是a - b


但是,在上面的示例中,每个表达式都会逐个执行。我认为你提出的情况与原始问题不同。我的意思是组运算符的优先级是在哪里应用的。 - Arnab Das
3
是的,它们被执行了,但 return 语句看不到它们,这就是您所询问的内容。 - Quentin
@ Quentin --- 你是说,在你的例子中,逗号分隔的每个表达式都会被执行? - Arnab Das
11
@ArnabDas — 是的。 a + b 没有显著影响,因为它没有副作用并且值被舍弃了(虽然考虑到它没有实际影响,优化编译器可能会将其删除,但这并没有实际影响)。 - Quentin
1
@Roman 是正确的。除了在条件语句中像 if 一样,跳过某个表达式不执行的唯一方法是短路运算。逗号操作符不会短路,因此它将计算两侧并返回最后一个。这是逗号操作符的明确目的,将表达式分组在单个语句中(通常忽略整个语句的结果值),最常用于变量初始化:var i = 2, j = 3, k=4; - Jason
显示剩余5条评论

32

我有点震惊,这里没人直接引用规范:

12.9 return语句 语法:ReturnStatement : return ; return [不跟随行终止符] Expression ;

语义

如果 ECMAScript 程序中包含一个不在函数体内的 return 语句,则认为该程序在语法上不正确。return 语句导致函数停止执行并将一个值返回给调用者。如果省略 Expression,则返回值为 undefined。否则,返回值就是 Expression 的值。

ReturnStatement 的求值如下:

如果没有 Expression 存在,返回 (return, undefined, empty)。 让 exprRef 成为评估表达式的结果。 返回 (return, GetValue(exprRef), empty)

因此,根据规范,您的示例应该写成:

return ( GetValue(exprRef) )

其中 exprRef = console.log(a + b), console.log(arguments)

根据逗号运算符的规范...

语义

表达式:Expression,AssignmentExpression 的产生式求值如下:

Let lref be the result of evaluating Expression.
Call GetValue(lref).
Let rref be the result of evaluating AssignmentExpression.
Return GetValue(rref).
...意味着每个表达式将被评估,直到逗号列表中的最后一项,成为赋值表达式。因此,您的代码return (console.log(a + b) , console.log(arguments))将会:
1.) 打印出a+b的结果。
2.) 没有剩余的表达式可以执行,所以执行下一个表达式,它
3.) 打印arguments,由于console.log()没有指定返回语句,
4.) 将会被计算为undefined。
5.) 最终将undefined作为返回值返回给调用者。
因此,正确的答案是,return没有类型,它只返回某些表达式的结果。
下一个问题:
“那么,我们可以通过逗号分隔的表达式传递到返回语句中。这是一个函数吗?”
不是。在JavaScript中,逗号是运算符,定义了允许您将多个表达式组合成单行的方式,并按规范定义为返回您列表中最后一个表达式的计算表达式。
您还不相信我吗?
<script>
alert(foo());
function foo(){
    var foo = undefined + undefined;
    console.log(foo);
    return undefined, console.log(1), 4;
}
</script>

在这里玩弄那段代码并且改变列表中的最后一个值。 它将始终返回列表中的最后一个值,在您的情况下,它恰好是undefined。

对于您的最后一个问题,

从这开始,我们是否可以猜测JavaScript中的每个关键字最终都是一个函数?

同样,不是的。在语言中,函数有一个非常具体的定义。我不会在这里重印它,因为这个答案已经变得极长。


15

测试返回带括号的值时会发生什么:

function foo() {
    return (1, 2);
}

console.log(foo());

回答是2,因此似乎逗号分隔的值列表将评估为列表中的最后一个元素。

实际上,在这里括号是无关紧要的,它们是将操作分组而不是表示函数调用。可能令人惊讶的是,逗号在这里是合法的。我发现了一篇有趣的博客文章,介绍了逗号在这里的处理方式:

https://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/


我猜,类似于 return ([1, 2]) 这样的代码将会打印出两个值? - cst1992
1
那将返回一个数组。 - We Stan Test Coverage
console.log((1, 2)) - thedayturns

9

return不是一个函数,它是出现在函数中的连续体

想一下这个语句:alert(2 * foo(bar));,其中foo是一个函数的名称。当你评估它时,你会发现要暂时搁置语句的剩余部分,集中精力评估foo(bar)。你可以将你搁置的部分视为类似于alert (2 * _)的东西,其中有一个要填充的空白。当你知道foo(bar)的值后,你会继续进行。

你设置的东西是调用foo(bar)连续体

调用return将一个值提供给该连续体。

当你在foo内部评估函数时,foo的其余部分等待该函数归约为一个值,然后foo继续进行。你仍然有一个目标,即评估foo(bar),只是暂停了。

当你在foo内部评估return时,foo的任何部分都不会等待一个值。在你使用returnfoo内部,它不会归约为一个值。相反,它导致整个调用foo(bar)归约为一个值,并且目标“评估foo(bar)”被视为已完成并消失。

当你刚开始学习编程时,人们通常不会告诉你连续体。他们认为这是一个高级话题,仅仅是因为有些非常高级的东西最终可以用连续体来实现。但事实上,每次调用一个函数时,你始终在使用它们。


这种思维方式在函数式编程语言(如LISP和Haskell)中很常见,但我从未见过它在像Javascript这样的过程式语言中使用。大多数过程式语言将它们视为一种特殊情况,因此一般不被认为是延续。例如,在Javascript中,您不能保存 alert(2 * _) 的延续以备后用(有另一种表示法来做这种事情)。 - Cort Ammon
1
@CortAmmon 虽然我有点同意你的观点,但请注意 JavaScript 确实具有相当强的函数式背景(它最初是作为 Scheme 子集的实现而设计的,具有类似于 C 的语法),虽然它没有任何标准函数来操作 continuation(例如 call/cc),但在许多 JavaScript 库中使用 continuation passing style 是常见做法,因此在早期阶段理解这个概念对 JS 程序员非常有用。 - Jules
JavaScript确实没有一等continuations。从foo内部的return不是一个值; 你不能将它打包到数据结构中,分配给变量,等待一段时间然后稍后再喂它--尤其是你不能多次喂它。但是,就像我说的,这是高级话题。 - Nathan Ellis Rasmussen
同样地,使用continuations实现新的控制结构:高级。但是,尝试实现一个行为与内置的if()then{}else{}完全相同的my_if函数,即使允许在其中使用内置函数,仍然无法做到这一点--这并不是非常高级的内容,但对于你最终编写需要传递回调的代码来说,它是非常有益的学习资源。 - Nathan Ellis Rasmussen

6
这里的return是一个转移注意力的话题。也许更有趣的是下面的变化:
function add(a, b) {
  return (
    console.log(a + b),
    console.log(arguments)
  );
}

console.log(add(2, 2));

这将作为最后一行输出

undefined

由于这个函数实际上没有返回任何内容。(如果有第二个console.log的返回值,它将返回该返回值)。

就目前而言,这段代码与以下代码完全相同:

function add(a, b) {
    console.log(a + b);
    console.log(arguments);
}

console.log(add(2, 2));

1

理解return语句的有趣方式是通过void运算符。看一下这段代码:

var console = {
    log: function(s) {
      document.getElementById("console").innerHTML += s + "<br/>"
    }
}

function funReturnUndefined(x,y) {
   return ( void(x+y) );
}

function funReturnResult(x,y) {
   return ( (x+y) );
}

console.log( funReturnUndefined(2,3) );
console.log( funReturnResult(2,3) );
<div id="console" />

由于return语句需要一个参数,即[[expression]],并将其返回给调用栈中的调用者,即arguments.callee.caller,因此它将执行void(expression),然后返回undefined,这是void运算符的评估结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接