Почему `(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. (насколько я могу интерпретировать) не являются допустимыми нетерминалами, которые привели бы к принятию скобок.

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


person TERMtm    schedule 14.05.2017    source источник
comment
я бы предположил присваивание деструктуризации, но там только квадратные и фигурные скобки   -  person Cee McSharpface    schedule 15.05.2017
comment
Угадайте, что круглые скобки просто заставляют вычисление внутри, (foo) === foo, также у нас есть (true ? foo : bar) === foo; (оба правильные).   -  person skobaljic    schedule 15.05.2017
comment
Здесь оператор группировки, почему он недействителен? Вы также можете сделать foo = (5), например.   -  person Egor Stambakio    schedule 15.05.2017
comment
@wostex Здесь оператор группировки --- если бы это было так, это было бы синтаксически неверно.   -  person zerkms    schedule 15.05.2017
comment
@skobaljic в вашем примере (foo) === foo потому что значения равны. Впрочем, и там ссылка не имеет значения. (foo) в идеальном мире вернул бы содержащийся объект, а не ссылку foo, которая заменяется выражением =.   -  person TERMtm    schedule 15.05.2017
comment
Правильно, я проголосовал за ваш вопрос, тоже хочу знать, почему.   -  person skobaljic    schedule 15.05.2017
comment
@TERMtm не знаю, действительно ли я на правильном пути, но согласно спецификации (той, которую вы указали), есть второе правило лексера для 12.2.1.5, которое определяет, что CoverParenthesizedExpressionAndArrowParameterList квалифицируется как IsValidSimpleAssignmentTarget, что дает его внутреннюю expr.   -  person Cee McSharpface    schedule 15.05.2017
comment
Ответ: Потому что JavaScript.   -  person naiveai    schedule 15.05.2017
comment
(foo) в этом случае не является оператором группировки, точно так же, как вызов метода (obj.foo)() не имеет оператора группировки вокруг obj.foo. (0, obj.foo)(), напротив, содержит оператор группировки. Предыдущий вызов передает obj в качестве контекста this в foo (точно так же, как obj.foo()), последний вызов - нет. См. Почему оператор запятой изменяет this в вызове функции.   -  person Sebastian Simon    schedule 23.03.2021


Ответы (2)


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

Левая часть операции = представляет собой LeftHandSideExpression как вы правильно определили. Это можно отследить по различным уровням приоритета (NewExpression< /a>, MemberExpression) в < href="https://www.ecma-international.org/ecma-262/7.0/index.html#prod-PrimaryExpression" rel="noreferrer">PrimaryExpression, который, в свою очередь, может быть Cover­Parenthesized­Expression­And­Arrow­Parameter­List:

( Expression[In, ?Yield] )

(фактически, при анализе с целью PrimaryExpression это ParenthesizedExpression< /а>).

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

Это раннее Reference Error, если LeftHandSideExpression не является ни ObjectLiteral, ни ArrayLiteral и IsValidSimpleAssignmentTarget для LeftHandSideExpression< /em> равно false.

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

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

person Bergi    schedule 15.05.2017
comment
Хотя зачем им разрешать такой синтаксис. Будет ли какой-то конкретный вариант использования, когда это может быть полезно, а не сбивать с толку? - person xji; 20.05.2017
comment
@JIXiang Я не знаю, тем более что это не разрешено для всех шаблонов (например, деструктуризация объекта, где это было бы полезно). Может быть, это просто ошибка, которая произошла при определении грамматики покрытия параметра стрелочной функции. Вы должны спросить на es-обсудить. - person Bergi; 20.05.2017

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

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

Что я узнал из этого небольшого исследования, так это то, что конструкция (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 выражения.

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

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

TL;DR

Это то, что нужно.

Хотите узнать больше?

Ознакомьтесь с ответом @Bergi.

person TERMtm    schedule 14.05.2017
comment
Спасибо @dlatikay! Вы помогаете мне найти именно то, что мне было нужно, чтобы подтвердить мои назойливые подозрения. - person TERMtm; 15.05.2017
comment
Почему? Потому что вы можете просто игнорировать круглые скобки, в то время как утверждение остается совершенным. --- на самом деле нужна ссылка на стандарт. - person zerkms; 15.05.2017
comment
@zerkms Насколько я могу себе представить, нигде в спецификации не говорится об этом прямо. CoverParenthesizedExpressionAndArrowParameterList упоминается в ответе , однако подразумевает такое поведение. - person TERMtm; 15.05.2017
comment
Тогда ответ будет предположением ¯\_(ツ)_/¯ Поведение должно объясняться стандартом, в противном случае это либо специфика реализации, либо ошибка, либо неопределенное поведение. - person zerkms; 15.05.2017
comment
Присмотревшись, CoveredParenthesizedExpression явно указывает, что это производство вернет «лексический поток токенов», найденный внутри себя. Понимая, что это значит, данное поведение объясняется. Ссылку поправил, судя по твоему комментарию. - person TERMtm; 15.05.2017
comment
Что странно, так это то, что они не разрешали ({ … }) = …, то есть выражение деструктурирования в скобках. - person Bergi; 15.05.2017
comment
@Bergi, можешь помочь найти, как он это отрицает? Из ecma-international.org /ecma-262/7.0/ похоже, все должно быть в порядке. Где другая сложная часть? - person zerkms; 15.05.2017
comment
@zerkms Именно здесь - это дает false для литералов объектов, они не являются простой целью присваивания. (Тест в Chrome подтвердил, что я правильно понял :-D) - person Bergi; 15.05.2017
comment
@Bergi, но фигурные скобки в { ... } = - это не литерал объекта, а синтаксис деструктурирования объекта. Или, по-видимому, у него нет контекста за скобками. - person zerkms; 15.05.2017
comment
Скобки — это токены синтаксического анализатора, а не просто лексемы, и они не используются для того, чтобы «заставить лексер... как сгруппировать то, что он читает». Все это делает парсер. Тот факт, что упомянутое вами произведение достигается через скобки, доказывает это. - person user207421; 15.05.2017
comment
@zerkms Именно поэтому эта грамматическая обложка так сбивает с толку :-/ Это действительно ObjectLiteral в грамматике (но не инициализатор объекта) - они даже обратите внимание на это: продукция ObjectLiteral также используются в качестве кавер-грамматики для ObjectAssignmentPattern. И в оценке, они анализируют его как AssignmentPattern. Понятия не имею, почему это так сложно. - person Bergi; 15.05.2017
comment
Из любопытства я нашел это и что. Причина использования кавер-грамматик в том, что они упрощают написание синтаксического анализатора. Видимо, они забыли пользовательскую историю, читая и понимая спецификацию. - person Bergi; 15.05.2017
comment
Этот ответ звучит неправильно - неверно, что круглые скобки интерпретируются только для того, чтобы заставить лексер группировать то, что он читает. Это (группировка вещей в правильный AST) является работой синтаксического анализатора, а не лексера. - person Oliver Charlesworth; 15.05.2017
comment
Отредактировано, чтобы отразить предложения @EJP - person TERMtm; 15.05.2017