Que tipo é a palavra-chave 'return'?


95

Usamos instruções de retorno opcionalmente em funções JavaScript. É uma palavra-chave. Mas qual é o tipo real de returnsi mesmo. Na verdade fiquei confuso, vendo o exemplo:

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

add(2, 2);

Resultado:

4
[2, 2]

Portanto, podemos passar expressões separadas por vírgulas para a returninstrução. Isso é uma função?

E começando com isso, podemos adivinhar que todas as palavras-chave em JavaScript são, em última análise, uma função?

Escrevi um pequeno blog como uma essência dessa discussão. Você pode querer verificar isso aqui .


106
Bem quando você pensava que entendia JavaScript, algo assim apareceu ...
FP

12
Suportes não significam que você está chamando a função, por exemplo: var a = (2 + 2).
alexmac de

12
Um exemplo mais apropriado é var a = (1, 2)- isso resultará em ater o valor 2devido a como o operador vírgula opera (sim, vírgula é um operador matemático como +ou |etc, especificamente, no cálculo lambada, o operador vírgula implementa o combinador K)
slebetman

6
Então, a ação correta é desmascarar o equívoco, não votar negativamente.
el.pescado

5
@FlorianPeschka não realmente, esse é o comportamento padrão em qualquer linguagem de sabor C.
Djechlin de

Respostas:


129

Mas qual é o tipo real de 'retorno' em si.

Não tem um tipo, não é um valor.

A tentativa typeof return;vai te dar Unexpected token return.

Portanto, podemos passar expressões separadas por vírgulas para a instrução de retorno. Isso é uma função?

Não, embora parênteses possam ser usados ​​para chamar uma função, aqui eles são um operador de agrupamento contendo algumas expressões separadas por um operador vírgula .

Uma demonstração mais útil seria:

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

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

Que sai 0porque o resultado de a + bé ignorado (está no LHS do operador vírgula) e a - bé retornado.


Mas, no exemplo acima, cada uma das expressões são executadas, uma a uma. Acho que o caso que você declarou é diferente do que foi declarado na pergunta original. O que eu quero dizer é onde a precedência do operador de grupo é aplicada lá.
Arnab Das

3
@ArnabDas - Sim, eles são executados, mas returnnão consegue vê-los, que é o que você estava perguntando.
Quentin de

@ArnabDas - "O que eu quero dizer é onde a precedência do operador de grupo é aplicada lá." - Dentro do operador de agrupamento, é claro.
Quentin de

11
@ArnabDas - Sim. a + bapenas não tem efeito visível porque não tem efeitos colaterais e o valor é descartado (bem, dado que não tem efeito prático, é possível que um compilador otimizador possa removê-lo, mas isso não faz diferença prática).
Quentin de

1
@Roman isso é correto. A única maneira de "pular" uma expressão para que ela não seja executada (exceto dentro de um like condicional if) é a avaliação de curto-circuito. O operador vírgula não causa curto-circuito, por isso avalia os dois lados. Em seguida, ele retornará o último. Esse é explicitamente o objetivo do operador vírgula, para agrupar expressões em uma única instrução (geralmente o valor resultante de toda a instrução é ignorado), mais frequentemente usado para inicialização de variável:var i = 2, j = 3, k = 4;
Jason

32

Estou um pouco chocado que ninguém aqui tenha feito referência direta à especificação :

12.9 A sintaxe da instrução return ReturnStatement: return; return [sem LineTerminator aqui] Expression;

Semântica

Um programa ECMAScript é considerado sintaticamente incorreto se contém uma instrução de retorno que não está dentro de um FunctionBody. Uma instrução de retorno faz com que uma função cesse a execução e retorne um valor ao chamador. Se Expression for omitido, o valor de retorno será indefinido. Caso contrário, o valor de retorno é o valor de Expression.

Um ReturnStatement é avaliado da seguinte forma:

Se a Expressão não estiver presente, retorne (return, undefined, empty). Deixe exprRefser o resultado da avaliação da Expressão. Retorno (return, GetValue(exprRef), empty).

Então, por causa da especificação, seu exemplo lê:

return ( GetValue(exprRef) )

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

Que de acordo com as especificações do operador vírgula ...

Semântica

A produção Expression: Expression, AssignmentExpression é avaliada da seguinte forma:

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 expressão será avaliada até o último item da lista de vírgulas, que se torna a expressão de atribuição. Então, seu códigoreturn (console.log(a + b) , console.log(arguments)) vai

1.) imprimir o resultado de a + b

2.) Nada é deixado para executar, então execute a próxima expressão que

3.) imprime o arguments, e porqueconsole.log() não especifica uma instrução de retorno

4.) Avalia como indefinido

5.) Que é então devolvido ao chamador.

Portanto, a resposta correta é, return não tem um tipo, só retorna o resultado de alguma expressão.

Para a próxima pergunta:

Portanto, podemos passar expressões separadas por vírgulas para a instrução de retorno. Isso é uma função?

Não. A vírgula em JavaScript é um operador, definido para permitir que você combine várias expressões em uma única linha, e é definida pela especificação para retornar a expressão avaliada do que quer que seja por último em sua lista.

Você ainda não acredita em mim?

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

Brinque com esse código aqui e mexa com o último valor da lista. Ele sempre retornará o último valor da lista, no seu caso, éundefined.

Para sua pergunta final,

E começando com isso, podemos adivinhar que todas as palavras-chave em JavaScript são, em última análise, uma função?

Novamente, não. As funções têm uma definição muito específica na linguagem. Não vou reimprimi-lo aqui porque essa resposta já está ficando extremamente longa.


15

Testando o que acontece quando você retorna valores entre parênteses:

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

console.log(foo());

Dá a resposta 2, então parece que uma lista de valores separados por vírgulas é avaliada como o último elemento da lista.

Na verdade, os parênteses são irrelevantes aqui, eles estão agrupando operações em vez de significar uma chamada de função. O que é possivelmente surpreendente, porém, é que a vírgula é válida aqui. Eu encontrei uma postagem de blog interessante sobre como a vírgula é tratada aqui:

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


Eu acho então, algo como return ([1, 2])deve imprimir os dois valores?
cst1992 de

1
Isso seria retornar um array.
Cobertura do Teste de Stan de

console.log((1, 2))
dias

9

returnnão é uma função. É a continuação da função em que ocorre.

Pense na instrução em alert (2 * foo(bar));que fooé o nome de uma função. Ao avaliar, você vê que precisa deixar de lado o resto da afirmação por um momento para se concentrar na avaliação foo(bar). Você pode visualizar a parte que você deixa de lado como algo semelhante alert (2 * _), com um espaço em branco para preencher. Quando você sabe qual é o valor de foo(bar), você o pega novamente.

O que você deixou de lado foi a continuação da ligação foo(bar).

Chamando returnalimenta um valor a que a continuação.

Quando você avalia uma função interna foo, o restante de fooespera que essa função seja reduzida a um valor e, em seguida, é fooreiniciado. Você ainda tem uma meta para avaliar foo(bar), apenas pausada.

Quando você avalia returninternamente foo, nenhuma parte de fooespera por um valor. returnnão se reduz a um valor no local fooonde você o usou. Em vez disso, faz com que toda a chamada foo(bar) seja reduzida a um valor, e o objetivo "avaliarfoo(bar) " é considerada completa e perdida.

As pessoas geralmente não falam sobre continuações quando você é novo em programação. Eles pensam nisso como um tópico avançado, apenas porque não são coisas muito avançadas que as pessoas eventualmente, fazer com continuações. Mas a verdade é que você os usa o tempo todo, toda vez que chama uma função.


Esse tipo de pensamento é comum em linguagens funcionais, como LISP e Haskell, mas nunca vi isso ser usado em linguagens procedurais como javascript. Normalmente, não se pensa neles como continuações porque a maioria das linguagens procedurais os trata como um caso especial. Por exemplo, você não pode salvar a continuação de alert(2 * _)para uso posterior em Javascript (há uma notação diferente para fazer esse tipo de coisa).
Cort Ammon

1
@CortAmmon Embora eu concorde com você, observe que o Javascript tem uma base funcional bastante forte (foi originalmente planejado como uma implementação de um subconjunto Scheme com uma sintaxe semelhante à C), e embora não tenha nenhuma função padrão para manipular continuações (por exemplo, call / cc), é prática comum usar o estilo de passagem de continuação em muitas bibliotecas javascript, então entender o conceito em um estágio inicial é bastante útil para programadores JS.
Jules,

É verdade que Javascript não faz continuações de primeira classe . O returnde dentro foonão é um valor; você não poderia empacotá-lo em uma estrutura de dados, atribuí-lo a uma variável, esperar um pouco e então alimentar algo mais tarde - e você não poderia alimentá-lo mais de uma vez. Mas, como eu disse, tópicos avançados.
Nathan Ellis Rasmussen

Da mesma forma, implementar uma nova estrutura de controle usando continuações: avançado. Mas tentar implementar uma my_iffunção que se comporte como integrada if()then{}else{}, e não conseguir fazê-lo mesmo que se permita usar a função integrada - não terrivelmente avançada e extremamente instrutiva, especialmente se você vai terminar no código que passa por callbacks.
Nathan Ellis Rasmussen de

6

o returnaqui é um arenque vermelho. Talvez seja interessante a seguinte variação:

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

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

que sai como a última linha

undefined

porque a função não retorna nada. (Ele retornaria o valor de retorno do segundo console.log, se houvesse um).

Como está, o código é exatamente idêntico ao

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

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

1

Uma maneira interessante de entender a instrução return é por meio do operador void Dê uma olhada neste 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" />

Uma vez que a returninstrução recebe um argumento que é [[expression]]e retorna para o chamador na pilha, ou seja, arguments.callee.callerela será executada void(expression)e retornará undefineda avaliação do operador void.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.