Каковы правила автоматической вставки точек с запятой в JavaScript (ASI)?
Ну, во-первых, я должен спросить, зависит ли это от браузера.
Я читал, что если найден недопустимый токен, но раздел кода действителен до тех пор, пока этот недопустимый токен, точка с запятой вставляется перед токеном, если ему предшествует разрыв строки.
Тем не менее, общий пример, процитированный для ошибок, вызванных вставкой точки с запятой:
return
_a+b;
.. который, кажется, не следует этому правилу, поскольку _a будет действительным токеном.
С другой стороны, разрыв цепочек вызовов работает, как и ожидалось:
$('#myButton')
.click(function(){alert("Hello!")});
У кого-нибудь есть более подробное описание правил?
7 ответов
Прежде всего вы должны знать, на какие операторы влияет автоматическая вставка точки с запятой (для краткости также называемая ASI):
- пустое заявление
var
заявление- выражение выражения
do-while
заявлениеcontinue
заявлениеbreak
заявлениеreturn
заявлениеthrow
заявление
Конкретные правила ASI, описаны в спецификации §11.9.1 Правила автоматической вставки точек с запятой
Три случая описаны:
Когда жетон (
LineTerminator
или же}
), что запрещено грамматикой, перед ней вставляется точка с запятой, если:- Токен отделен от предыдущего токена хотя бы одним
LineTerminator
, - Знак является
}
например:
{ 1 2 } 3
превращается в
{ 1 ;2 ;} 3;
NumericLiteral
1
соответствует первому условию, следующий токен является ограничителем строки.2
соответствует второму условию, следующий токен}
,- Токен отделен от предыдущего токена хотя бы одним
Когда встречается конец входного потока токенов, и синтаксический анализатор не может проанализировать входной поток токенов как одну завершенную Программу, тогда точка с запятой автоматически вставляется в конец входного потока.
например:
a = b ++c
трансформируется в:
a = b; ++c;
Этот случай возникает, когда токен разрешен некоторым производством грамматики, но производство является ограниченным производством, точка с запятой автоматически вставляется перед ограниченным токеном.
Ограниченные производства:
UpdateExpression : LeftHandSideExpression [no LineTerminator here] ++ LeftHandSideExpression [no LineTerminator here] -- ContinueStatement : continue ; continue [no LineTerminator here] LabelIdentifier ; BreakStatement : break ; break [no LineTerminator here] LabelIdentifier ; ReturnStatement : return ; return [no LineTerminator here] Expression ; ThrowStatement : throw [no LineTerminator here] Expression ; ArrowFunction : ArrowParameters [no LineTerminator here] => ConciseBody YieldExpression : yield [no LineTerminator here] * AssignmentExpression yield [no LineTerminator here] AssignmentExpression
Классический пример с
ReturnStatement
:return "something";
превращается в
return; "something";
Я не мог понять эти 3 правила в спецификациях слишком хорошо - надеюсь, что у меня будет что-то более простое в английском языке - но вот что я собрал из JavaScript: Полное руководство, 6-е издание, Дэвид Фланаган, О'Рейли, 2011:
Цитата:
JavaScript не обрабатывает каждый разрыв строки как точку с запятой: он обычно обрабатывает разрывы строки как точки с запятой, только если он не может проанализировать код без точек с запятой.
Другая цитата: для кода
var a
a
=
3 console.log(a)
JavaScript не обрабатывает второй разрыв строки как точку с запятой, потому что он может продолжить анализ более длинного оператора a = 3;
а также:
два исключения из общего правила, что JavaScript интерпретирует разрывы строк как точки с запятой, когда он не может проанализировать вторую строку как продолжение оператора в первой строке. Первое исключение включает в себя операторы return, break и continue
... Если после любого из этих слов появляется разрыв строки... JavaScript всегда будет интерпретировать разрыв строки как точку с запятой.
... Второе исключение включает операторы ++ и −−... Если вы хотите использовать любой из этих операторов в качестве постфиксных операторов, они должны отображаться в той же строке, что и выражение, к которому они применяются. В противном случае разрыв строки будет рассматриваться как точка с запятой, а ++ или - будет проанализирован как префиксный оператор, примененный к следующему коду. Рассмотрим этот код, например:
x
++
y
Он анализируется как
x; ++y;
не какx++; y
Я думаю, чтобы упростить это, это означает:
В общем случае JavaScript будет воспринимать его как продолжение кода, если это имеет смысл - за исключением двух случаев: (1) после некоторых ключевых слов, таких как return
, break
, continue
и (2) если он видит ++
или же --
на новой строке, то он добавит ;
в конце предыдущей строки.
Часть о том, что "рассматривайте его как продолжение кода, пока он имеет смысл", создает ощущение жадного соответствия регулярного выражения.
С учетом вышесказанного, это означает, что для return
с переводом строки интерпретатор JavaScript вставит ;
(цитируется еще раз: если после любого из этих слов появляется разрыв строки [такой как return
] ... JavaScript всегда будет интерпретировать разрыв строки как точку с запятой)
и по этой причине классический пример
return
{
foo: 1
}
не будет работать должным образом, потому что интерпретатор JavaScript будет обрабатывать его как:
return; // returning nothing
{
foo: 1
}
Не должно быть разрыва строки сразу после return
:
return {
foo: 1
}
чтобы он работал правильно. И вы можете вставить ;
сами, если бы вы следовали правилу использования ;
после любого заявления:
return {
foo: 1
};
Прямо из ECMA-262, спецификация ECMAScript пятого издания:
7.9.1 Правила автоматической вставки точек с запятой
Существует три основных правила вставки точек с запятой:
- Когда при синтаксическом анализе программы слева направо обнаруживается токен (называемый токеном-нарушителем), который не допускается никаким производством грамматики, тогда точка с запятой автоматически вставляется перед токеном-нарушителем, если один или несколько из следующих условия верны:
- Токен-нарушитель отделен от предыдущего токена хотя бы одним
LineTerminator
,- Токен-нарушитель - }.
- Когда, когда программа анализируется слева направо, встречается конец входного потока токенов, и синтаксический анализатор не может проанализировать входной поток токенов как один полный ECMAScript
Program
затем точка с запятой автоматически вставляется в конец входного потока.- Когда, когда программа анализируется слева направо, встречается токен, который допускается некоторым производством грамматики, но производство является ограниченным производством, и токен будет первым токеном для терминала или нетерминала, следующего сразу за аннотацией. " [нет
LineTerminator
здесь] "в ограниченном производстве (и, следовательно, такой токен называется ограниченным токеном), и ограниченный токен отделяется от предыдущего токена хотя бы одним LineTerminator, затем точка с запятой автоматически вставляется перед ограниченным токеном.Однако в предыдущих правилах есть дополнительное переопределяющее условие: точка с запятой никогда не вставляется автоматически, если точка с запятой будет затем проанализирована как пустая инструкция, или если эта точка с запятой станет одной из двух точек с запятой в заголовке инструкции for (см. 12.6.3).
Что касается вставки точки с запятой и оператора var, остерегайтесь забывать запятую при использовании var, но охватывающую несколько строк. Кто-то нашел это в моем коде вчера:
var srcRecords = src.records
srcIds = [];
Он запустился, но эффект состоял в том, что объявление / присваивание srcIds было глобальным, потому что локальное объявление с var на предыдущей строке больше не применялось, так как этот оператор считался завершенным из-за автоматической вставки точек с запятой.
Наиболее контекстное описание автоматической вставки точки с запятой в JavaScript, которое я нашел, взято из книги о крафтовых интерпретаторах.
Правило JavaScript "автоматической вставки точки с запятой" является странным. В то время как другие языки предполагают, что большинство новых строк имеют смысл, и только некоторые из них следует игнорировать в многострочных операторах, JS предполагает обратное. Он обрабатывает все ваши символы новой строки как бессмысленные пробелы, если не встречается ошибка синтаксического анализа. Если это так, он возвращается и пытается превратить предыдущий перевод строки в точку с запятой, чтобы получить что-то грамматически правильное.
Он продолжает описывать это, как если бы вы кодировали запах.
Эта заметка о дизайне превратилась бы в обличительную речь, если бы я подробно остановился на том, как это вообще работает, не говоря уже о различных способах, которыми это плохая идея. Это беспорядок. JavaScript - единственный язык, который я знаю, где многие руководства по стилю требуют явной точки с запятой после каждого оператора, даже если язык теоретически позволяет вам их опустить.
Просто добавлю,
const foo = function(){ return "foo" } //this doesn't add a semicolon here.
(function (){
console.log("aa");
})()
увидеть это, используя выражение немедленно вызываемой функции (IIFE)
Большинство операторов и объявлений в JavaScript должны заканчиваться точкой с запятой, однако для удобства программиста (меньше печатать, стилистические предпочтения, меньше шума в коде, более низкий барьер для входа) точка с запятой может быть опущена в некоторых местах исходного текста, с среда выполнения автоматически вставляет точки с запятой в соответствии с набором правил, изложенных в спецификации.
Общие правила: точка с запятой никогда не вставляется автоматически, если точка с запятой будет затем проанализирована как пустой оператор или если эта точка с запятой станет одной из двух точек с запятой в заголовке оператора.for
заявление.
Правило 1
Точка с запятой будет автоматически вставлена, если синтаксический анализатор JavaScript встретит токен, который не был бы разрешен, если бы точка с запятой не существовала, и этот токен отделен от предыдущего одним или несколькими разделителями строк (например, новой строкой), закрывающим скобка}
, или последняя скобка ()
) цикла do-while.
Другими словами: места исходного текста, где операторы всегда должны быть завершены в любом случае для запускаемой программы, будут иметь терминатор оператора (;
) вставляется автоматически, если он опущен. Это правило является сердцем ASI.
Правило 2
В конце программы будет вставлена точка с запятой, если исходный текст не является действительным сценарием или модулем. Другими словами: программисты могут опускать последнюю точку с запятой в программе.
Правило 3
Точка с запятой будет автоматически вставлена, если встречается токен, который обычно разрешается, если точка с запятой не существует, но существует в одном из нескольких специальных местоположений исходного текста ( ограниченное производство ), которые явно запрещают использование в них разделителей строк из соображений избежания двусмысленности.
Ограниченные производства, внутри которых запрещены терминаторы строк:
- перед постфиксом
++
и постфикс--
(поэтому унарные операторы увеличения/уменьшения после новой строки будут привязаны к следующему ( не предыдущему) оператору в качестве префиксного оператора) - после , , , ,
- после списков параметров функции стрелки и
- после ключевого слова в объявлениях и выражениях асинхронных функций, объявлениях, выражениях и методах функций-генераторов и асинхронных стрелочных функциях
Спецификация содержит полную информацию , а также следующие практические советы:
В результате получается практический совет программистам ECMAScript:
Постфиксный оператор ++ или -- должен находиться в той же строке, что и его операнд.
Выражение в операторе return или throw или выражение AssignmentExpression в выражении должно начинаться с той же строки, что и выражение.
return
,throw
, илиyield
токен.LabelIdentifier в операторе или должен находиться в той же строке, что и
break
илиcontinue
токен.Конец параметра(ов) стрелочной функции и его
=>
должны быть на одной линии.The
async
токен, предшествующий асинхронной функции или методу, должен находиться в той же строке, что и непосредственно следующий за ним токен.
И это лучшая статья в Сети на эту тему .
Примеры ASI Gotcha
Начиная строку с `(`
Открывающая скобка имеет несколько значений. Он может обозначать выражение или указывать на вызов (в сочетании с закрывающей скобкой).
Например, следующее выдает «Uncaught TypeError: console.log(...) is not a function», потому что среда выполнения пытается вызвать возвращаемое значение:
let a = 'foo'
console.log('bar')
(a = 'bam')
Одно из решений для этого, если вы обычно опускаете точки с запятой, состоит в том, чтобы включить точку с запятой, чтобы сделать ваши намерения однозначными:
let a = 'foo'
console.log('bar')
;(a = 'bam') // note semicolon at start of line
Начало строки с `[`
Символ открывающей скобки ([
) имеет несколько значений. Он может указывать на доступ к свойству объекта, может указывать на буквальное объявление массива (в сочетании с закрывающей скобкой) или может указывать на деструктуризацию массива.
Например, следующее выдает «Uncaught TypeError: Невозможно установить свойства неопределенного (настройка «foo»)», потому что среда выполнения пытается установить значение свойства с именем «foo» в ответеconsole.log('bar')
:
let a = 'foo'
console.log('bar')
[a] = ['bam']
Одно из решений для этого, если вы обычно опускаете точки с запятой, состоит в том, чтобы включить точку с запятой, чтобы сделать ваши намерения однозначными:
let a = 'foo'
console.log('bar')
;[a] = ['bam'] // note semicolon at start of line