Почему `(foo) = "bar"` допустимо в JavaScript?

В REPL Node.js (проверено также в SpiderMonkey) последовательность

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

действителен, с foo впоследствии равный "bar" в отличие от null,

Это кажется нелогичным, потому что можно было бы подумать, что в скобках будет по крайней мере разыменование bar и бросить Неверную левую сторону в задании`.

Понятно, что, когда вы делаете что-то интересное, оно терпит неудачу вышеупомянутым способом.

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

Согласно ECMA-262 на LeftHandExpressions (насколько я могу интерпретировать) нет допустимых нетерминалов, которые привели бы к принятию скобок.

Есть что-то, чего я не вижу?

2 ответа

Решение

Это действительно так. Вы можете заключить любую простую цель назначения в круглые скобки.

Левая часть = операция LeftHandSideExpression как вы правильно определили. Это можно отследить через различные уровни приоритета (NewExpression, MemberExpression) к PrimaryExpression что, в свою очередь, может быть Cover­Parenthesized­Expression­And­Arrow­Parameter­List:

 ( Выражение [В,? Доходность] ) 

(на самом деле, когда анализируется с целью PrimaryExpression, это ParenthesizedExpression).

Так что это верно по грамматике, по крайней мере. Действительный ли это синтаксис JS определяется другим фактором: статическая семантика ранних ошибок. Это в основном прозаические или алгоритмические правила, которые в некоторых случаях делают некоторые производственные расширения недействительными (синтаксические ошибки). Это, например, позволило авторам повторно использовать грамматики инициализатора массива и объекта для деструктурирования, но применяя только определенные правила. В ранних ошибках для выражений присваивания мы находим

Это рано Reference Error если LeftHandSideExpression не является ни ObjectLiteral, ни ArrayLiteral, а IsValidSimpleAssignmentTarget of LeftHandSideExpression имеет значение false,

Мы также можем увидеть это различие в оценке выражений присваивания, где простые цели присваивания оцениваются для ссылки, которая может быть назначена, вместо того, чтобы получать вещи шаблона деструктурирования, такие как литералы объекта и массива.

Так что же делает IsValidSimpleAssignmentTarget для LeftHandSideExpressions? По сути, он позволяет присваивать доступ к свойствам и запрещает присваивания для выражений вызовов. В нем ничего не говорится о простых PrimaryExpressions, у которых есть собственное правило IsValidSimpleAssignmentTarget. Все, что он делает, это извлекает Expression между скобками посредством операции CoveredParenthesizedExpression, а затем снова проверяет IsValidSimpleAssignmentTarget этого. Короче: (…) = … действует, когда … = … является действительным. Это даст true только для идентификаторов (как в вашем примере) и свойств.

Согласно предложению @dlatikay, после существующего предчувствия, исследование CoveredParenthesizedExpression дал лучшее понимание того, что здесь происходит.

Видимо, причина, по которой нетерминал не может быть найден в спецификации, объясняет почему (foo) приемлемо как LeftHandExpression На удивление просто. Я предполагаю, что вы понимаете, как работают парсеры, и что они работают в два отдельных этапа: Lexing и Parsing.

Из этого небольшого исследования я узнал, что конструкция (foo) технически не доставляется парсеру, а потому и движку, как вы могли подумать.

Рассмотрим следующее

var foo = (((bar)));

Как мы все знаем, что-то подобное совершенно законно. Зачем? Ну, визуально вы можете просто игнорировать скобки, в то время как это утверждение имеет смысл.

Аналогичным образом, вот еще один действительный пример, даже с точки зрения читаемости человеком, потому что круглые скобки объясняют только то, что PEMDAS уже делает неявным.

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

Из этого можно получить одно ключевое наблюдение, учитывая понимание того, как парсеры уже работают. (помните, Javascript все еще анализируется ( читай: скомпилирован), а затем запускается). Таким образом, в прямом смысле эти скобки "указывают очевидное".

Так что, как говорится, что именно происходит?

По сути, круглые скобки (за исключением параметров функции) свернуты в соответствующие группы содержащих их символов. IANAL, но, с точки зрения непрофессионала, это означает, что скобки интерпретируются только как руководство для синтаксического анализатора, как группировать то, что он читает. Если контекст круглых скобок уже находится "в порядке" и, следовательно, не требует какой-либо настройки выдаваемого AST, то (машинный) код выводится так, как если бы эти скобки вообще не существовали.

Синтаксический анализатор более или менее ленив, полагая, что паренсы наглые. (что в этом крайнем случае не соответствует действительности)

Хорошо, а где именно это происходит?

Согласно 12.2.1.5 Статическая семантика: IsValidSimpleAssignmentTarget в спецификации,

PrimaryExpression: (CoverParenthesizedExpressionAndArrowParameterList)

  1. Пусть expr будет CoveredParenthesizedExpression of CoverParenthesizedExpressionAndArrowParameterList.
  2. Возврат IsValidSimpleAssignmentTarget из expr.

IE если ожидаешь primaryExpression верните все, что находится в скобках, и используйте это.

Из-за этого в этом сценарии он не преобразует (foo) в CoveredParenthesizedExpression{ inner: "foo" }, он превращает его просто в foo что сохраняет тот факт, что это Identifier и, следовательно, синтаксически, хотя и не обязательно лексически, допустимо.

TL; DR

Это Ват.

Хотите немного больше понимания?

Проверьте ответ @ Берги.

Другие вопросы по тегам