Pourquoi `(foo) =” bar “` légal en JavaScript?

Dans REPL de Node.js (testé également dans SpiderMonkey), la séquence

var foo = null; (foo) = "bar"; 

est valide, avec foo par la suite égal à "bar" par opposition à null .

Cela semble contre-intuitif, car on pourrait penser que la parenthèse serait au moins déréférencée par une bar et lancerait le côté gauche invalide dans l’affectation .

Naturellement, lorsque vous faites quelque chose d’intéressant, cela échoue de la manière susmentionnée.

 (foo, bar) = 4 (true ? bar : foo) = 4 

Selon ECMA-262 sur LeftHandExpressions (pour autant que je puisse interpréter), il n’y a pas de non-terminaux valides qui permettraient d’accepter une parenthèse.

Y a-t-il quelque chose que je ne vois pas?

C’est valide en effet. Vous êtes autorisé à envelopper toute cible d’affectation simple entre parenthèses.

La partie gauche de l’opération = est une LeftHandSideExpression que vous avez correctement identifiée. Cela peut être suivi à travers les différents niveaux de NewExpressionMemberExpression ( NewExpression , MemberExpression ) vers une PrimaryExpression , qui peut à son tour être une CoverParenthesizedExpressionAndArrowParameterList :

  ( Expression [In,? Yield] ) 

(en fait, quand elle est analysée avec la cible PrimaryExpression , c’est une PrimaryExpression ParenthesizedExpression ).

Donc c’est au moins par la grammaire. La syntaxe JS valide est déterminée par un autre facteur: la sémantique statique des erreurs précoces. Ce sont essentiellement des règles de prose ou d’algorithme qui rendent certaines extensions de production invalides (erreurs de syntaxe) dans certains cas. Cela a par exemple permis aux auteurs de réutiliser le tableau et les grammaires d’initialisation d’object pour les déstructurer, mais seulement en appliquant certaines règles. Dans les premières erreurs pour les expressions d’affectation, nous trouvons

Il s’agit d’une Reference Error précoce si LeftHandSideExpression n’est ni un ObjectLiteral ni un ArrayLiteral et que IsValidSimpleAssignmentTarget de LeftHandSideExpression est false .

Nous pouvons également voir cette distinction dans l’ évaluation des expressions d’affectation , où les cibles d’affectation simples sont évaluées à une référence pouvant être assignée, au lieu d’obtenir les éléments du modèle de déstructuration tels que les littéraux d’object et de tableau.

Alors, que fait IsValidSimpleAssignmentTarget pour LeftHandSideExpressions ? Fondamentalement, il permet d’affecter des access aux propriétés et interdit les affectations aux expressions d’appel. Il n’indique rien à propos des PlainExpressions simples qui ont leur propre règle IsValidSimpleAssignmentTarget . Tout ce qu’il fait est d’extraire l’ expression entre les parenthèses via l’ opération CoveredParenthesizedExpression , puis de vérifier à nouveau IsValidSimpleAssignmentTarget. En bref: (…) = … est valide lorsque … = … est valide. Cela ne sera true que pour les identifiants (comme dans votre exemple) et les propriétés.

Selon la suggestion de @dlatikay , suite à une intuition existante, les recherches sur CoveredParenthesizedExpression permis de mieux comprendre ce qui se passe ici.

Apparemment, la raison pour laquelle un non-terminal ne peut pas être trouvé dans la spécification , pour expliquer pourquoi (foo) est acceptable en tant que LeftHandExpression , est étonnamment simple. Je suppose que vous comprenez comment fonctionnent les parsingurs et qu’ils fonctionnent en deux étapes distinctes: Lexing et Parsing .

Ce que j’ai appris de cette petite tangente de recherche, c’est que la construction (foo) n’est techniquement pas livrée à l’parsingur, et donc le moteur, comme vous pourriez le penser.

Considérer ce qui suit

 var foo = (((bar))); 

Comme nous le soaps tous, quelque chose comme cela est parfaitement légal. Pourquoi? Eh bien, vous pouvez simplement ignorer la parenthèse tandis que la déclaration rest parfaitement logique.

De même, voici un autre exemple valable, même dans une perspective de lisibilité humaine, car les parenthèses n’expliquent que ce que PEMDAS rend déjà implicite.

 (3 + ((4 * 5) / 2)) === 3 + 4 * 5 / 2 >> true 

Une observation clé peut en être dérivée, compte tenu de la façon dont les parsingurs fonctionnent déjà. (rappelez-vous, le javascript est toujours analysé ( lu: compilé ), puis exécuté) Donc, dans un sens direct, ces parenthèses “indiquent l’évidence” .

Donc, tout cela étant dit, que se passe-t-il exactement?

Fondamentalement, les parenthèses (à l’exception des parameters de fonction) sont regroupées en groupes appropriés de leurs symboles. IANAL mais, en termes profanes, cela signifie que les parenthèses ne sont interprétées que pour guider l’parsingur sur la manière de regrouper ce qu’il lit. Si le contexte des parenthèses est déjà “dans l’ordre” et ne nécessite donc aucun ajustement de l’ AST émise, alors le code (machine) est émis comme si ces parenthèses n’existaient pas du tout.

L’parsingur est plus ou moins paresseux, en supposant que les parens sont impertinents. (ce qui dans ce cas n’est pas vrai)

Ok, et où cela se passe-t-il exactement?

Selon 12.2.1.5 Sémantique statique: IsValidSimpleAssignmentTarget dans la spécification,

PrimaryExpression: (CoverParenthesizedExpressionAndArrowParameterList)

  1. Soit expr l’expression CoveredParenthesizedExpression de CoverParenthesizedExpressionAndArrowParameterList.
  2. Renvoie IsValidSimpleAssignmentTarget de expr.

IE Si s’attendre à primaryExpression renvoie tout ce qui est à l’intérieur de la parenthèse et l’utilise.

A cause de cela, dans ce scénario, il ne convertit pas (foo) en CoveredParenthesizedExpression{ inner: "foo" } , il le convertit simplement en foo ce qui préserve le fait qu’il est un Identifier et donc syntaxiquement, bien que pas nécessairement lexicalement valide. .

TL; DR

C’est wat.

Vous voulez un peu plus de perspicacité?

Découvrez la réponse de @ Bergi .