¿Qué tipo es la palabra clave 'retorno'?

95

Usamos declaraciones de retorno opcionalmente en funciones de JavaScript. Es una palabra clave. Pero, ¿cuál es el tipo real de returnsí mismo? En realidad me confundí al ver el ejemplo:

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

add(2, 2);

Salida:

4
[2, 2]

Entonces, podemos pasar expresiones separadas por comas a la returndeclaración. ¿Es esta una función?

Y comenzando con esto, ¿podemos suponer que cada palabra clave en JavaScript es en última instancia una función?

He escrito un pequeño blog como parte esencial de esta discusión. Es posible que desee comprobarlo aquí .

Arnab Das
fuente
106
Justo cuando pensaba que entendía JavaScript, aparece algo como esto ...
FP
12
Soportes no significan que usted está llamando a la función, por ejemplo: var a = (2 + 2).
alexmac
12
Un ejemplo más apropiado es var a = (1, 2): esto resultará en atener el valor 2debido a cómo opera el operador de coma (sí, la coma es un operador matemático como +o |etc., específicamente, en el cálculo lambada, el operador de coma implementa el combinador K)
slebetman
6
Entonces, la acción correcta es desacreditar el concepto erróneo, no votar en contra.
el.pescado
5
@FlorianPeschka no realmente, este es el comportamiento estándar en cualquier lenguaje de sabor C.
djechlin

Respuestas:

129

Pero, ¿cuál es el tipo real de "devolución" en sí?

No tiene un tipo, no es un valor.

Intentarlo typeof return;te dará Unexpected token return.

Entonces, podemos pasar expresiones separadas por comas en la declaración de retorno. ¿Es esta una función?

No, mientras que los paréntesis se pueden usar para llamar a una función, aquí son un operador de agrupación que contiene un par de expresiones separadas por un operador de coma .

Una demostración más útil sería:

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

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

Qué salidas 0porque el resultado de a + bse ignora (está en el LHS del operador de coma) y a - bse devuelve.

Quentin
fuente
Pero, en el ejemplo anterior, cada una de las expresiones se ejecuta, una por una. Creo que el caso que planteó es diferente al planteado en la pregunta original. Lo que quiero decir es dónde se aplica la precedencia del operador de grupo allí.
Arnab Das
3
@ArnabDas - Sí, se ejecutan, pero returnno llega a verlos, que es lo que estabas preguntando.
Quentin
@ArnabDas - "Lo que quiero decir es dónde se aplica la precedencia del operador de grupo". - Dentro del operador de agrupación, por supuesto.
Quentin
11
@ArnabDas - Sí. a + bsimplemente no tiene ningún efecto visible porque no tiene efectos secundarios y el valor se descarta (bueno, dado que no tiene ningún efecto práctico, es posible que un compilador optimizador lo elimine, pero eso no hace ninguna diferencia práctica).
Quentin
1
@Roman eso es correcto. La única forma de "omitir" una expresión para que no se ejecute (además de dentro de un condicional como if) es la evaluación de cortocircuito. El operador de coma no cortocircuita, por lo que evaluará ambos lados. Luego devolverá el último. Ese es explícitamente el propósito del operador de coma, agrupar expresiones en una sola declaración (generalmente se ignora el valor resultante de toda la declaración), que se usa con mayor frecuencia para la inicialización de variables:var i = 2, j = 3, k = 4;
Jason
32

Estoy un poco sorprendido de que nadie aquí haya hecho referencia directa a la especificación :

12.9 La sintaxis de la instrucción return ReturnStatement: return; return [no LineTerminator aquí] Expresión;

Semántica

Un programa ECMAScript se considera sintácticamente incorrecto si contiene una declaración de retorno que no está dentro de FunctionBody. Una declaración de retorno hace que una función deje de ejecutarse y devuelva un valor al llamador. Si se omite Expresión, el valor de retorno no está definido. De lo contrario, el valor de retorno es el valor de Expresión.

Un ReturnStatement se evalúa de la siguiente manera:

Si la expresión no está presente, regrese (return, undefined, empty). Sea exprRefel resultado de evaluar Expression. Regreso (return, GetValue(exprRef), empty).

Entonces, debido a la especificación, su ejemplo dice:

return ( GetValue(exprRef) )

dónde exprRef = console.log(a + b), console.log(arguments)

Que de acuerdo con la especificación del operador de coma ...

Semántica

La producción Expression: Expression, AssignmentExpression se evalúa de la siguiente manera:

Let lref be the result of evaluating Expression.
Call GetValue(lref).
Let rref be the result of evaluating AssignmentExpression.
Return GetValue(rref).

... significa que cada expresión se evaluará hasta el último elemento de la lista de comas, que se convierte en la expresión de asignación. Entonces tu código return (console.log(a + b) , console.log(arguments))va a

1.) imprime el resultado de a + b

2.) No queda nada por ejecutar, así que ejecute la siguiente expresión que

3.) imprime arguments, y porque console.log()no especifica una declaración de devolución

4.) Evalúa como indefinido

5.) Que luego se devuelve a la persona que llama.

Entonces, la respuesta correcta es, returnno tiene un tipo, solo devuelve el resultado de alguna expresión.

Para la siguiente pregunta:

Entonces, podemos pasar expresiones separadas por comas en la declaración de retorno. ¿Es esta una función?

No. La coma en JavaScript es un operador, definido para permitirle combinar múltiples expresiones en una sola línea, y está definida por la especificación para devolver la expresión evaluada de lo que sea el último en su lista.

¿Aún no me crees?

<script>
alert(foo());
function foo(){
    var foo = undefined + undefined;
    console.log(foo);
    return undefined, console.log(1), 4;
}
</script>

Juega con ese código aquí y juega con el último valor de la lista. Siempre devolverá el último valor de la lista, en su caso, resulta serundefined.

Para su pregunta final,

Y comenzando con esto, ¿podemos suponer que cada palabra clave en JavaScript es en última instancia una función?

De nuevo, no. Las funciones tienen una definición muy específica en el lenguaje. No lo volveré a imprimir aquí porque esta respuesta ya se está volviendo extremadamente larga.

avgvstvs
fuente
15

Probando lo que sucede cuando devuelve valores entre paréntesis:

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

console.log(foo());

Da la respuesta 2, por lo que parece que una lista de valores separados por comas se evalúa como el último elemento de la lista.

Realmente, los paréntesis son irrelevantes aquí, están agrupando operaciones en lugar de significar una llamada a función. Sin embargo, lo que posiblemente sea sorprendente es que la coma es legal aquí. Encontré una publicación de blog interesante sobre cómo se maneja la coma aquí:

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

SpoonMeiser
fuente
Entonces, supongo que algo como ¿ return ([1, 2])debería imprimir ambos valores?
cst1992
1
Eso estaría devolviendo una matriz.
Cobertura de prueba de We Stan
console.log((1, 2))
thedayturns
9

returnno es una función. Es la continuación de la función en la que ocurre.

Piense en el enunciado alert (2 * foo(bar));donde fooestá el nombre de una función. Cuando lo evalúa, ve que tiene que dejar de lado el resto de la declaración por un momento para concentrarse en evaluar foo(bar). Podrías visualizar la parte que apartaste como algo así alert (2 * _), con un espacio en blanco para completar. Cuando sabes cuál es el valor de foo(bar), retíralo.

Lo que dejó de lado fue la continuación de la llamada foo(bar).

Llamar le returnda valor a esa continuación.

Cuando evalúas una función en el interior foo, el resto de fooespera a que esa función se reduzca a un valor y luego la fooreanuda. Todavía tienes un objetivo que evaluar foo(bar), simplemente está en pausa.

Cuando evalúas el returninterior foo, ninguna parte de fooespera por un valor. returnno se reduce a un valor en el lugar interior foodonde lo usó. En cambio, hace que toda la llamada se foo(bar) reduzca a un valor, y el objetivo "evaluar foo(bar)" se considera completo y deslumbrado.

La gente no suele informarle sobre las continuaciones cuando es nuevo en la programación. Ellos piensan que es un tema avanzado, simplemente porque no son cosas muy avanzadas que las personas eventualmente hacen con continuaciones. Pero la verdad es que los está usando todo el tiempo, cada vez que llama a una función.

Nathan Ellis Rasmussen
fuente
Este tipo de pensamiento es común con lenguajes funcionales, como LISP y Haskell, pero nunca lo he visto usado en lenguajes procedimentales como javascript. Normalmente, no se los considera como continuaciones porque la mayoría de los lenguajes de procedimiento los tratan como un caso especial. Por ejemplo, no puede guardar la continuación de alert(2 * _)para su uso posterior en Javascript (hay una notación diferente para hacer ese tipo de cosas).
Cort Ammon
1
@CortAmmon Si bien estoy de acuerdo con usted, tenga en cuenta que Javascript tiene una base funcional bastante sólida (originalmente se pensó como una implementación de un subconjunto de Scheme con una sintaxis similar a C), y aunque no tiene funciones estándar para manipular continuaciones (por ejemplo, call / cc) es una práctica común usar el estilo de paso de continuación en muchas bibliotecas de JavaScript, por lo que comprender el concepto en una etapa temprana es bastante útil para los programadores de JS.
Jules
Es cierto que Javascript no hace continuaciones de primera clase . El returndesde adentro foono es un valor; no podía empaquetarlo en una estructura de datos, asignarlo a una variable, esperar un tiempo y luego alimentarlo con algo más tarde, y especialmente no podía alimentarlo más de una vez. Pero, como dije, temas avanzados.
Nathan Ellis Rasmussen
Así mismo, implementar una nueva estructura de control mediante continuaciones: avanzada. Pero intentar implementar una my_iffunción que se comporte como una función incorporada if()then{}else{}, y no hacerlo incluso si te permites usar la función incorporada dentro de ella, no es terriblemente avanzada y enormemente instructiva, especialmente si vas a terminar en código que transmite devoluciones de llamada.
Nathan Ellis Rasmussen
6

el returnaquí es una pista falsa. Quizás sea interesante la siguiente variación:

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

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

que sale como la última línea

undefined

ya que la función en realidad no devuelve nada. (Devolvería el valor de retorno del segundo console.log, si tuviera uno).

Tal como está, el código es exactamente idéntico a

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

console.log(add(2, 2));
esto
fuente
1

Una forma interesante de entender la declaración de retorno es a través del operador void . Eche un vistazo a este código.

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" />

Dado que la returndeclaración toma un argumento que es [[expression]]y lo devuelve a la persona que llama en la pila, es decir arguments.callee.caller, se ejecutará void(expression)y luego devolverá, undefinedesa es la evaluación del operador void.

Loretoparisi
fuente