Каковы правила автоматической вставки точек с запятой в JavaScript (ASI)?

Ну, во-первых, я должен спросить, зависит ли это от браузера.

Я читал, что если найден недопустимый токен, но раздел кода действителен до тех пор, пока этот недопустимый токен, точка с запятой вставляется перед токеном, если ему предшествует разрыв строки.

Тем не менее, общий пример, процитированный для ошибок, вызванных вставкой точки с запятой:

return
  _a+b;

.. который, кажется, не следует этому правилу, поскольку _a будет действительным токеном.

С другой стороны, разрыв цепочек вызовов работает, как и ожидалось:

$('#myButton')
  .click(function(){alert("Hello!")});

У кого-нибудь есть более подробное описание правил?

7 ответов

Решение

Прежде всего вы должны знать, на какие операторы влияет автоматическая вставка точки с запятой (для краткости также называемая ASI):

  • пустое заявление
  • var заявление
  • выражение выражения
  • do-while заявление
  • continue заявление
  • break заявление
  • return заявление
  • throw заявление

Конкретные правила ASI, описаны в спецификации §11.9.1 Правила автоматической вставки точек с запятой

Три случая описаны:

  1. Когда жетон (LineTerminator или же }), что запрещено грамматикой, перед ней вставляется точка с запятой, если:

    • Токен отделен от предыдущего токена хотя бы одним LineTerminator,
    • Знак является }

    например:

    { 1
    2 } 3
    

    превращается в

    { 1
    ;2 ;} 3;
    

    NumericLiteral1 соответствует первому условию, следующий токен является ограничителем строки.
    2 соответствует второму условию, следующий токен },

  2. Когда встречается конец входного потока токенов, и синтаксический анализатор не может проанализировать входной поток токенов как одну завершенную Программу, тогда точка с запятой автоматически вставляется в конец входного потока.

    например:

    a = b
    ++c
    

    трансформируется в:

    a = b;
    ++c;
    
  3. Этот случай возникает, когда токен разрешен некоторым производством грамматики, но производство является ограниченным производством, точка с запятой автоматически вставляется перед ограниченным токеном.

    Ограниченные производства:

    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 Правила автоматической вставки точек с запятой

Существует три основных правила вставки точек с запятой:

  1. Когда при синтаксическом анализе программы слева направо обнаруживается токен (называемый токеном-нарушителем), который не допускается никаким производством грамматики, тогда точка с запятой автоматически вставляется перед токеном-нарушителем, если один или несколько из следующих условия верны:
    • Токен-нарушитель отделен от предыдущего токена хотя бы одним LineTerminator,
    • Токен-нарушитель - }.
  2. Когда, когда программа анализируется слева направо, встречается конец входного потока токенов, и синтаксический анализатор не может проанализировать входной поток токенов как один полный ECMAScript Program затем точка с запятой автоматически вставляется в конец входного потока.
  3. Когда, когда программа анализируется слева направо, встречается токен, который допускается некоторым производством грамматики, но производство является ограниченным производством, и токен будет первым токеном для терминала или нетерминала, следующего сразу за аннотацией. " [нет 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
Другие вопросы по тегам