Quel est le type du mot-clé "retour"?


95

Nous utilisons des instructions de retour en option dans les fonctions JavaScript. C'est un mot-clé. Mais quel est le type réel de returnlui - même. En fait, je suis devenu confus en voyant l'exemple:

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

add(2, 2);

Production:

4
[2, 2]

Ainsi, nous pouvons passer des expressions séparées par des virgules dans l' returninstruction. Est-ce une fonction?

Et en commençant par cela, pouvons-nous supposer que chaque mot-clé en JavaScript est finalement une fonction?

J'ai écrit un petit blog pour l'essentiel de cette discussion. Vous voudrez peut-être le vérifier ici .


106
Juste au moment où vous pensiez comprendre JavaScript, quelque chose comme ça se produit ...
FP

12
Les crochets ne signifient pas que vous appelez la fonction, par exemple: var a = (2 + 2).
alexmac

12
Un exemple plus approprié est var a = (1, 2)- cela aboutira à aavoir la valeur en 2raison de la façon dont l'opérateur virgule fonctionne (oui, la virgule est un opérateur mathématique comme +ou |etc, en particulier, dans le calcul lambada, l'opérateur virgule implémente le combinateur K)
slebetman

6
Ensuite, l'action correcte est de démystifier l'idée fausse, et non de voter contre.
el.pescado

5
@FlorianPeschka pas vraiment, c'est un comportement standard dans n'importe quel langage C-flavour.
djechlin

Réponses:


129

Mais quel est le type réel de «retour» lui-même.

Il n'a pas de type, ce n'est pas une valeur.

Tenter typeof return;vous donnera Unexpected token return.

Ainsi, nous pouvons passer des expressions séparées par des virgules dans l'instruction return. Est-ce une fonction?

Non, alors que les parenthèses peuvent être utilisées pour appeler une fonction, il s'agit ici d'un opérateur de regroupement contenant quelques expressions séparées par une virgule .

Une démonstration plus utile serait:

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

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

Quelles sorties 0parce que le résultat de a + best ignoré (il est sur la LHS de l'opérateur virgule) et a - best retourné.


Mais, dans l'exemple ci-dessus, chacune des expressions est exécutée, une par une. Je pense que le cas que vous avez présenté est différent de ce qui est indiqué dans la question initiale. Ce que je veux dire, c'est où la priorité de l'opérateur de groupe est appliquée ici.
Arnab Das

3
@ArnabDas - Oui, ils sont exécutés, mais ils returnne peuvent pas les voir, ce que vous demandiez.
Quentin

@ArnabDas - "Ce que je veux dire, c'est où la priorité de l'opérateur de groupe est appliquée là-bas." - Au sein de l'opérateur de regroupement bien sûr.
Quentin

11
@ArnabDas - Oui. a + bn'a simplement aucun effet visible car il n'a pas d'effets secondaires et la valeur est ignorée (enfin, étant donné qu'elle n'a aucun effet pratique, il est possible qu'un compilateur optimisant puisse la supprimer, mais cela ne fait aucune différence pratique).
Quentin

1
@Roman c'est correct. La seule façon de "sauter" une expression afin qu'elle ne soit pas exécutée (à part dans un conditionnel comme if) est l'évaluation de court-circuit. L'opérateur virgule ne court-circuite pas, il évaluera donc les deux côtés. Ensuite, il retournera le dernier. C'est explicitement le but de l'opérateur virgule, de regrouper les expressions dans une seule instruction (généralement la valeur résultante de l'instruction entière est ignorée), le plus souvent utilisé pour l'initialisation des variables:var i = 2, j = 3, k = 4;
Jason

32

Je suis un peu choqué que personne ici n'ait directement référencé la spécification :

12.9 La syntaxe de l'instruction return ReturnStatement: return; return [aucun LineTerminator ici] Expression;

Sémantique

Un programme ECMAScript est considéré comme syntaxiquement incorrect s'il contient une instruction return qui ne se trouve pas dans un FunctionBody. Une instruction return provoque l'arrêt de l'exécution d'une fonction et renvoie une valeur à l'appelant. Si Expression est omis, la valeur de retour n'est pas définie. Sinon, la valeur de retour est la valeur de Expression.

Un ReturnStatement est évalué comme suit:

Si l'expression n'est pas présente, retournez (return, undefined, empty). Soit exprRefle résultat de l'évaluation d'Expression. Retour (return, GetValue(exprRef), empty).

Donc, à cause de la spécification, votre exemple se lit comme suit:

return ( GetValue(exprRef) )

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

Qui selon la spécification sur l'opérateur virgule ...

Sémantique

L'expression de production: Expression, AssignmentExpression est évaluée comme suit:

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

... signifie que chaque expression sera évaluée jusqu'au dernier élément de la liste de virgules, qui devient l'expression d'affectation. Donc ton codereturn (console.log(a + b) , console.log(arguments)) va

1.) imprimer le résultat de a + b

2.) Il ne reste plus rien à exécuter, alors exécutez l'expression suivante qui

3.) imprime le arguments, et parce queconsole.log() ne spécifie pas une instruction de retour

4.) Évalue à undefined

5.) Qui est ensuite renvoyé à l'appelant.

Donc la bonne réponse est, return n'a pas de type, elle ne renvoie que le résultat d'une expression.

Pour la question suivante:

Ainsi, nous pouvons passer des expressions séparées par des virgules dans l'instruction return. Est-ce une fonction?

Non. La virgule en JavaScript est un opérateur, défini pour vous permettre de combiner plusieurs expressions en une seule ligne, et est défini par la spécification pour renvoyer l'expression évaluée de ce qui est en dernier dans votre liste.

Vous ne me croyez toujours pas?

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

Jouez avec ce code ici et dérangez la dernière valeur de la liste. Il renverra toujours la dernière valeur de la liste, dans votre cas, il se trouve que c'estundefined.

Pour votre dernière question,

Et en commençant par cela, pouvons-nous supposer que chaque mot-clé en JavaScript est finalement une fonction?

Encore une fois, non. Les fonctions ont une définition très spécifique dans la langue. Je ne la réimprimerai pas ici car cette réponse devient déjà extrêmement longue.


15

Test de ce qui se passe lorsque vous renvoyez des valeurs entre parenthèses:

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

console.log(foo());

Donne la réponse 2, de sorte qu'il semble qu'une liste de valeurs séparées par des virgules s'évalue jusqu'au dernier élément de la liste.

Vraiment, les parenthèses ne sont pas pertinentes ici, elles regroupent des opérations au lieu de signifier un appel de fonction. Ce qui est peut-être surprenant, cependant, c'est que la virgule est légale ici. J'ai trouvé un article de blog intéressant sur la façon dont la virgule est traitée ici:

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


Je suppose alors, quelque chose comme return ([1, 2])devrait imprimer les deux valeurs?
cst1992

1
Ce serait retourner un tableau.
Couverture du test We Stan le

console.log((1, 2))
déroule le

9

returnn'est pas une fonction. C'est la continuation de la fonction dans laquelle il se produit.

Pensez à l'instruction alert (2 * foo(bar));fooest le nom d'une fonction. Lorsque vous l'évaluez, vous voyez que vous devez mettre de côté le reste de l'énoncé pendant un moment pour vous concentrer sur l'évaluation foo(bar). Vous pouvez visualiser la partie que vous avez mise de côté comme quelque chose comme alert (2 * _), avec un espace à remplir. Lorsque vous savez quelle est la valeur defoo(bar) , vous la reprenez.

La chose que vous avez mise de côté était la poursuite de l'appel foo(bar).

L'appel donne returnune valeur à cette continuation.

Lorsque vous évaluez une fonction à l' intérieur foo, le reste fooattend que cette fonction se réduise à une valeur, puis fooreprend. Vous avez encore un objectif à évaluer foo(bar), il est juste mis en pause.

Lorsque vous évaluez à l' returnintérieur foo, aucune partie de foon'attend une valeur. returnne se réduit pas à une valeur à l'endroit à l'intérieur foooù vous l'avez utilisé. Au lieu de cela, cela entraîne la réduction de l' appel entier foo(bar)à une valeur et l'objectif "évaluerfoo(bar) " est considéré comme complet et époustouflé.

Les gens ne vous parlent généralement pas des suites lorsque vous êtes nouveau dans la programmation. Ils le considèrent comme un sujet avancé, simplement parce qu'il y a des choses très avancées que les gens finissent par faire avec des suites. Mais la vérité est que vous les utilisez tout le temps, chaque fois que vous appelez une fonction.


Ce type de pensée est courant avec les langages fonctionnels, comme LISP et Haskell, mais je ne l'ai jamais vu utilisé dans des langages procéduraux comme javascript. On ne les considère généralement pas comme des suites car la plupart des langages procéduraux les traitent comme un cas particulier. Par exemple, vous ne pouvez pas enregistrer la continuation de alert(2 * _)pour une utilisation ultérieure en Javascript (il existe une notation différente pour faire ce genre de chose).
Cort Ammon

1
@CortAmmon Bien que je sois en quelque sorte d'accord avec vous, notez que Javascript a un fond fonctionnel assez solide (il était à l'origine conçu comme une implémentation d'un sous-ensemble de schéma avec une syntaxe de type C), et bien qu'il n'ait pas de fonctions standard pour manipuler les continuations (par exemple, call / cc), il est courant d'utiliser le style de passage de continuation dans de nombreuses bibliothèques javascript, donc comprendre le concept à un stade précoce est très utile pour les programmeurs JS.
Jules

Il est vrai que Javascript ne fait pas de continuations de première classe . L' returnintérieur foon'est pas une valeur; vous ne pouviez pas le regrouper dans une structure de données, l'assigner à une variable, attendre un moment, puis y ajouter quelque chose plus tard - et vous ne pouviez surtout pas le nourrir plus d'une fois. Mais, comme je l'ai dit, des sujets avancés.
Nathan Ellis Rasmussen

De même, implémentation d'une nouvelle structure de contrôle à l'aide de continuations: avancée. Mais essayer d'implémenter une my_iffonction qui se comporte comme une fonction intégrée if()then{}else{}, et ne pas le faire même si vous vous autorisez à utiliser la fonction intégrée à l'intérieur - pas très avancée, et extrêmement instructive, surtout si vous allez finir dans le code qui transmet les rappels.
Nathan Ellis Rasmussen

6

le returnvoici un hareng rouge. La variante suivante est peut-être intéressante:

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

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

qui sort comme dernière ligne

undefined

car la fonction ne renvoie réellement rien. (Il renverrait la valeur de retour du secondconsole.log , s'il en avait une).

Tel quel, le code est exactement identique à

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

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

1

Un moyen intéressant de comprendre l' instruction return est d' utiliser l' opérateur void Jetez un œil à ce code

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

Puisque l' returninstruction prend un argument qui est [[expression]]et le retourne à l'appelant sur la pile, c'est- arguments.callee.callerà- dire qu'il s'exécutera void(expression)puis retournera, undefinedc'est l'évaluation de l'opérateur void.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.