Пользователь
0,0
рейтинг
10 января 2011 в 15:56

Разработка → Всё, что надо знать о точке с запятой перевод

Автовставка точек с запятой (";") — одна из наиболее спорных особенностей яваскрипта, вокруг которой скопилось много непонимания.

Некоторые программисты ставят ";" в конце каждого оператора, некоторые — только там, где строго необходимо. Большинство же где-то посередине, хотя есть и такие, которые добавляют лишние ";" из стилистических соображений.

Даже если вы всегда ставите ";" в конце каждого оператора, некоторые конструкции парсятся неочевидным образом. Вне зависимости от ваших предпочтений касательно ";", правила такого парсинга надо знать, чтобы использовать язык профессионально. Запомнив несколько простых правил, приведённых ниже, вы поймёте, как будет парситься любая программа, и станете экспертом в автовставке ";" в яваскрипте.



Где допустимы ТЗ



В формальной грамматике, данной в спецификации ECMAscript, ";" имеются в конце каждого оператора, где они могут быть. Вот оператор do-while:

do Statement while ( Expression );

ТЗ также возникают в грамматике на конце операторов var, операторов-выражений (наподобие «4+4;» или «f();»), операторов continue, return, break, throw и операторов отладчика.

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

Иногда пустые операторы полезные, хотя бы синтаксически. Например, для бесконечного цикла можно написать «while(1);», точка с запятой парсится как пустой оператор, делая оператор while синтаксически валидным. Без ТЗ оператор while был бы неполон, поскольку после условия цикла необходим оператор.

Наконец, ";" используются в циклах в форме «for ( Выражение; Выражение; Выражение ) Оператор», и разумеется, могут использоваться в строчных и регексповых литералах.

Где точку с запятой можно пропустить



В формальной грамматике из спецификаций ECMAscript ";" упомянуты, как описано выше. Однако спецификация также даёт правила, описывающие, как реальный парсинг отличается от формальной грамматики. Правила описаны через воображаемые ";", вставляемые во входной поток, но это всего лишь спецификационная модель, на практике парсерам не нужно генерировать псевдо-";", а можно воспринимать ";" как опциональные в определённых местах грамматике (см., например, грамматику парсинга в ECMAscript, в особенности правила Statement, EOS, EOSnoLB, и SnoLB). Везде, где спецификация говорит «вставляется »;"", имеется в виду, что текущий оператор заканчивается.

Правила автовставки ТЗ описаны в разделе 7.9 ECMA-262 [pdf].

Раздел даёт три правила и два исключения из них.

Правила таковы:

Когда программа встречает токен, недопустимый грамматикой, вставляется ";", если (а) в этом месте присутствует перенос строки, или (б) недопустимый токен является закрывающей фигурной скобкой. При достижении конце файла и невозможности иной интерпретации, вставляется ";". При появлении «ограниченного порождения» [restricted production], содержащего терминатор строки в месте, где грамматика говорит "[no LineTerminator here]", вставляется ";".

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

Исключения: ";" никогда не вставляется в заголовок цикла вида «for ( Выражение; Выражение; Выражение ) Оператор», и ";" никогда не вставляется, если в результате получается пустой оператор.

Что это всё для нас означает?

Прежде всего, ";" опциональна только на конце строки, перед закрывающей фигурной скобкой, и в конце программы. Кром того, ";" не предполагается на конце строки, если первый токен следующей строки может быть отпарсен как часть предыдущего оператора.

«42; "hello!"» пример валидной программы, равно как и «42\n"hello!"» (где "\n" изображает перенос строки) но «42 "hello!"» уже нет, так как перенос строки вызывает автовставку ";", а пробел нет. «if(x){y()}» также валидно. Здесь «y()» есть оператор-выражение, который может оканчиватся ";", но поскольку следующий токен это закрывающая фигурная скобка, ";" опциональна, несмотря на отсутствие переноса строки.

Оба исключения, для циклов и пустого оператора, можно проиллюстрировать вместе:

for (node=getNode();
     node.parent;
     node=node.parent) ;


Цикл последовательно вызывает следующий родительский узел, пока не не найдётся узел без родителя. Всё это происходит в заголовке цикла, так что для тела цикла ничего не осталось. Однако синтаксис цикла требует оператор, и мы вставляем пустой оператор. Несмотря на то, что все три ";" в этом примере на концах строк, все три необходимы, так как ";" не вставляется в заголовках цикла или для создания пустого оператора.

Ограниченное порождение



В ограниченных порождениях перенос строки не может находится в определённых местах, потому что если в этих местах разбить строку, программа не отпарсится так же, но может отпарсится иначе.

В грамматике пять ограниченных порождений, это постфиксные операторы ++ и --, операторы continue, break и return. Операторы break и continue могут иметь опциональный идентификатор для передачи управления из конкретного цикла. При использовании этой возможности идентификатор обязан быть на той же строке. Это валидная программа:

var c,i,l,quitchars
quitchars=['q','Q']
charloop:while(c=getc()){
    for (i=0; i<quitchars.length; i++){
        if (c==quitchars[i]) break charloop
    }
    /* ... код для других символов ... */
}


Если getc() возвращает символ из входного потока, программа читает его, проверяет, не символ ли выхода это, и если да, передаёт управление за цикл. Помеченный оператор break нужен, чтобы вырваться за внешний цикл, а не только за внутренний. Такая же программа, различающаяюся лишь переносом строки, не даст тот же результат:

var c,i,l,quitchars
quitchars=['q','Q']
charloop:while(c=getc()){
    for (i=0; i<quitchars.length; i++){
        if (c==quitchars[i]) break
            charloop
    }
    /* ... код для других символов ... */
}


В этом случе токен charloop — не часть оператора break. Так как оператор break ограничен, перенос строки в этой позиции завершает оператор. Токен charloop парсится просто как переменная charloop, но управление это место так и не получит, а оператор break будет выходить из внутреннего цикла, а не из внешнего, как задумано.

Примеры остальных четырёх ограниченных порождений:

// PostfixExpression :                                            
//              LeftHandSideExpression [no LineTerminator here] ++
//              LeftHandSideExpression [no LineTerminator here] --
var i=1;
i
++;


Это выдаст ошибку, и не отпарсит как «i++». Терминатор не может отделять постфиксный оператор инкремента или декремента, так что "++" или "--" в начале строки никогда не отпарсится как часть предыдущей строки.

i
++
j


А это не ошибка, отпарсится как «i; ++j». Префисные инкремент и декремент не являются ограниченным порождением, поэтому перенос строки может встретиться между "++" или "--" и модифицируемым ими выражением.

// ReturnStatement: return [no LineTerminator here] Expressionopt ;
return
  {i:i, j:j}


Это отпарсится как пустой оператор return, вслед за которым идёт оператор-выражение, до которого управление никогда не дойдёт. А вот это отпарсится так, как задумано:

return {
  i:i, j:j}
return (
  {i:i, j:j})
return {i:i
       ,j:j}


Отметьте, что оператор return МОЖЕТ содержать переносы внутри выражения, только лишь не между токеном return и началом выражения. При намеренном пропуске ";", ограниченное порождение оператора return удобно, так как позволяет написать пустой return без того, чтобы случайно вернуть выражение со следующей строки:

function initialize(a){
  // если a уже инициализированно, вернуться
  if(a.initialized) return
  a.initialized = true
  /* ... инициализировать a ... */
}


Операторы continue и throw похожи на break и return:

continue innerloop // верно
 
continue
    innerloop;     // неверно
// ThrowStatement : throw [no LineTerminator here] Expression ;
throw                                          // ошибка разбора
  new MyComplexError(a, b, c, more, args);
// В отличии от return, break, continue, 
// выражение после throw обязательно, 
// поэтому вышеприведённое неотпарсится вообще.
throw new MyComplexError(a, b, c, more, args); // верно
throw new MyComplexError(
    a, b, c, more, args);                      // тоже верно
// любой вариант с throw и new на одной строке верен.


Отступы не играют роли для парсинга программ на ECMAscript, а наличие или отсутстсвие переносов строки играет. Тем самым, любой процессор исходного текста на яваскрипте может вырезать начальные пробелы (кроме строковых констант!) без влияния на семантику программы, но переносы строк нельзя произвольно вырезать или заменять пробелами или точками с запятой. Минификатор, меняющей семантику валидных программ есть плохой, негодный минификатор, и единственный способ это написать полный и корректный парсер.

Переносы строк после return, break, continue и перед ++ и — влияют на парсинг. Поскольку ограничены только эти порождения, то пробелы и переносы строк могут быть свободно использованы в любом другом месте для улучшения читабельности программы. В частности, логические, арифметические, операторы строчной конкатенации, тройной (либо условный) оператор, доступ к члену через точку или скобки, вызовы функций, циклы while, for, операторы switсh, и остальные контрольные структуры могут быть записаны с разрывом строк где угодно.

Спецификация гласит:

Практический совет для программистов ECMAScript: постфиксные операторы "++" и "--" должны быть на одной строке со своим операндом. Выражение в операторах return или throw должно начинаться на одной строке с токеном return или throw. Идентификатор в операторе break или continue должен быть на одной строке с токеном break или continue.


Наиболее частая ошибка программиста при ограниченных порождениях — ставить возвращаемое значение на строку после токена return, особенно если возвращается большой объект или литерал массива, или многострочная константа. Ошибки с постфиксными операторами, и операторами break, continue, throw на практике редки, в силу того, что такое разбиение строки выглядит неестественным для большинства программистов.

Последняя тонкость автовставки ";" вытекает из первого правила, требующего, чтобы программа содержала недопустимый токен для вставки ";". Если вы пропускаете опциональные ";", помните, что есть и неопциональные, которые пропускать нельзя. Это правило позволяет растягивать операторы на несколько строк:

return obj.method('abc')
          .method('xyz')
          .method('pqr')
 
return "длинная строка\n"
     + "растянулась\n"
     + "на несколько"
 
totalArea = rect_a.height * rect_a.width
          + rect_b.height * rect_b.width
          + circ.radius * circ.radius * Math.PI


Правило касается лишь первого токена в строке. Если этот токен может отпарсится как часть оператора, то оператор продолжается (даже если парсинг не удастся дальше). Если же первый токен не может продолжить оператор, начинается следующий (в этом месте спецификация говорит «вставляется »;"").

Потенциал для ошибок возникает, когда в паре операторов А и Б оба по отдельности валидны, но первый токен Б может также быть принят как продолжение А. В таких случаях, при остутствии ";", парсер не разберёт Б как отдельный оператор, и либо выдаст ошибку, либо отпарсит программу неожиданным образом. Таким образом, если ";" пропускаются, программисту нужно следить за любыми операторами А и Б, разделёнными переносом строки, не начинается ли Б с токена, который можно пристроить к концу А.

Большинство операторов в яваскрипте начинается идентификатором, а большинство остальных — ключевым словом наподобие «var», «function», «if». Для любого такого оператора Б, начинающего с идентификатора или ключевого слова, равно как и для любой строки, начинающейся со строковой константы, валидного оператора А не существует (доказательство сего из грамматики языка оставлено как упражнение для читателя).

A
function f(x){return x*x}
 
// для любого оператора А без ТЗ
// все эти примеры парсятся верно
 
A
f(7)
 
A
"a string".length


К сожалению, есть пять токенов, которые могут как начинать оператор, так и продолжать уже завершённый. Это "(", "[", "/", "+" и "-". На практике проблемы вызывают первые два.

Это значит, что не всегда перенос строки может заменить ";" между операторами.

Спецификация даёт пример:
                   a = b + c
                   (d + e).print()

не преобразуется автовставкой ";", так как выражение в скобках может быть отпарсено как аргумент вызова функции:
                   a = b + c(d + e).print()

Спецификация предлагает, «когда оператор присваивания должен начинаться левой скобкой, неплохо явно поставить точку с запятой на предыдущей строке». Более строгой альтернативой является практика постановки ТЗ в начале строке, непосредственно перед токеном, рискующим внести двусмысленность:
                   a = b + c
                   ;(d + e).print()

Операторы, начинающиеся круглой или квадратной скобкой, нечасты, но встречаются.

Примеры с квадратной скобкой более часты, так как «функциональные» операции наподобие map, filter, forEach более часты при массивах. Часто удобно записать массивный литерал с forEach, нужным для своих побочных эффектов:
[['January','Jan']
,['February','Feb']
,['March','Mar']
,['April','Apr']
,['May','May']
,['June','Jun']
,['July','Jul']
,['August','Aug']
,['September','Sep']
,['October','Oct']
,['November','Nov']
,['December','Dec']
].forEach(function(a){ print("The abbreviation of "+a[0]+" is "+a[1]+".") })
 
['/script.js'
,'/style1.css'
,'/style2.css'
,'/page1.html'
].forEach(function(uri){
   log('Looking up and caching '+uri)
   fetch_and_cache(uri)})

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

Последний проблемный токен это слеш, и он весьма неинтуитивен. Взгляните:
var i,s
s="here is a string"
i=0
/[a-z]/g.exec(s)

В строчках 1-3 мы заводим переменные, а на четвёртой мы вроде как пишем регекспный литерал "/[a-z]/g", который глобально находит a-z, а потом мы вызываем этот регексп со строкой методом exec. Так как возвращаемое значение exec() не используется, код не особо полезен, но мы бы ожидали, что он хотя бы скомпилируется. Однако же, слеш не только начинает регексп, но и является оператором деления. Это означает, что начальный слеш на строке 4 будет отпарсен как прдолжение оператора присваивания на предыдущей строке. Эти строки отпарсятся как «i равно 0 делить на [a-z] делить на g.exec(s)».

На практике эта проблема почти никогда не возникает, так как причин начинать оператор регекспом немного. В примере выше, значение вызова exec() обычно бы передавалось функции или присваивалось переменной, в любом из случаев строка бы не начиналась слешем. Возможное исключение это, опять же, метод forEach, который можно с пользой использовать [оригинал: usefully used] на значении, возвращённом вызовом exec().

Операторы "+" и "-" могут быть использованы как унарные, для преобразования значения к типу Number, и для реверсии знака в случае "-". При использовании в начале строки при пропущенных ";" они могут быть восприняты как соответствующие бинарные операторы, и продолжение предыдущего оператора. Но и это редко составляет проблему, так как начальный унарный оператор встречается ещё реже, чем регексп (и он, к тому же, не выглядит завершённо). Как и с регекспами, если бы программист хотел привести значение к числу, он бы это значение как-то использовал, присвоил бы переменной, или передал функции, и ни в каком из этих случаев унарный оператор не был бы в начале:
var x,y,z
x = +y;    // полезно
y = -y;    // полезно
print(-y); // полезно
+z;        // бесполезно

Во всех таких случаях, если вы пропускаете ";", безопасной практикой является начинать строки со скобкой как раз точкой с запятой. Тот же совет для маловероятных случаев операторов "+", "-", или слеша. Таким образом, даже если ТЗ не используются везде, строка будет защищена от неверного парсинга вне зависимости от того, как может измениться предыдущая строка.

Заблуждения



Многие начинающие программисты на яваскрипте получают советы ставить ";" везде, и полагают, что если они не используют правила автовставки ";", это свойство языка можно игнорировать. Это не так, из-за правил ограниченного порождения, приведённых выше, в особенности оператора return. А когда они знакомятся с ограниченным порождением, они начинаются боятся переносов строк, и избегают из даже когда они улучшают читабельность. Лучше всего освоить правила автовставки ";", чтобы мочь читать любой код, и мочь писать код наиболее ясным образом.

Ещё одно заблуждение гласит, что баги в броузерных движках яваскрипта означают, что ставить точки с запятой везде надёжнее, и что это повышает совместимость. Это просто не так. Все существующие броузеры реализуют спецификацию корректно в отношении автовставки ";", и любые баги, которые могли существовать, давно ушли во мрак ранней истории веба. Беспокоиться о бразуерной совместимости нет причин: все броузеры имплементируют эти правила так, так изложено выше.

Заключение



Ставить ли точки с запятой? Дело ваше. Просто выбор должен делаться на основании информации, а не туманных страхов по поводу неведомых синтаксических ловушек либо несуществующих багов броузеров. Если вы запомните эти правила, вы вооружены для верного выбора, и будет легко читать код на яваскрипте.

Если вы решили не ставить ";", советую вам ставить их перед открывающими скобками в операторах, которые ими начинаются, и в операторах, которые начинаются с "/", "+", "-", если вам доведётся такой оператор написать.

Вне зависимости от точек с запятой, помните правила ограниченного порождения (return, break, continue, throw, и постфиксные операторы инкремента и декремента), и можете разбивать строки в любых других местах для удобства и читаемости кода.
Перевод: inimino
Iļja Ketris @bubuq
карма
68,3
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (84)

  • +36
    На практике надо ставить везде, ибо все так или иначе сжимают скрипты. И отсутствие точки с запятой может вызвать ошибку в сжатом скрипте, которую крайне сложно найти.
    • 0
      При обфускации убираются лишние символы? Или лучше делать вручную?
      • +1
        YUI оптимизирует/сжиает названия переменных, методов, которые не достаются из замыкания/функции.
    • +6
      Популярный пример:
      var a = function () {}
      a.prototype.b = 100;
      alert((new a()).b);

      В первой строчке можно и не ставить ";" все работает.
      Сжимаем это дело YUI, получаем фактически неисправимую ошибку (SyntaxError: missing; before statement on line 1):
      var a=function(){}a.prototype.b=100;alert((new a()).b);

      Этот код маленький, и вы легко найдете и исправите ошибку. На практике это сделать невозможно: «SyntaxError: missing; before statement on line 1», а вся строка длинной 50Кб где начнем искать? :)
      Поэтому перед сжатием я советую обработать скрипт JSLint'ом
      • +5
        Ваш пример после применения yuicompressor был преобразован в абсолютно корректный код:
        var a=function(){};a.prototype.b=100;alert((new a()).b);
        


        Я никогда не ставил ";" после "}" и никогда не было проблем с YUI. Так что это Вы зря. Тем не менее, JSLint лишним не будет.
        • +1
          И правда, недавно начал использовать YUI не знал, что он такой умный (код в примере сжал руками). JsMin, например, портил код.
          Протестировал другой проблемный код:
          if (window)
          window.a = 100;
          else 
          a = 100;
          b = 100
          на выходе все верно
          if(window){window.a=100}else{a=100}b=100;

          Чтож, респект Yahoo! за YUI
      • 0
        > На практике это сделать невозможно: «SyntaxError: missing; before statement on line 1», а вся строка длинной 50Кб где начнем искать? :)

        Запросто:
        Часто встречаемая ошибка это как раз таки пропущенная ТЗ после определения функции. Так что достаточно легко ищется (Особенно если программа-редактор подсвечивает парные скобки). Таким образом нужно найти function, для него найти закрывающую кавычку и проверить есть ли за ней ';'.

        Про JSLint абсолютно согласен.
      • 0
        поэтому стоит отказаться от этой кривой поделки и воспользоваться gcc, а не добавлять костыль в виде линта.
  • 0
    Легче всего следить так: пишешь один оператор на строку, ставишь после него точку с запятой (кроме каких-нибудь исключений). Если хочется сжать скрипт по размеру — в конце убираешь все переносы строк, а ТЗ при этом будут расставлены верно.
    • +1
      Легче всего писать код так, чтобы потом не было больно его читать и поддерживать =)
      А если хочется сжать, то наверно лучше использовать компрессор. Он и названия переменных минимизурет и ненужные скобки поубирает, а может и function inlining сделает если это уменьшит размер кода.

      Кстати, jQuery с относительно недавних пор использует Google Closure Compiler вместо YUI compressor.
      Но у YUI комрессора есть одно преимущество — он и CSS сжимает заодно, не надо отдельный компрессор держать для CSS.
  • 0
    А что это за мода такая пошла переносить запятую в начало следующей строки при перечислении, например, имен переменных? Очень неприятно такой код читать (как отступы перед знаками препинания, получается).
    • +1
      Это чтобы случайно не поставить запятую после последнего элемента.
      Мне тоже кажется, что тут решение проблемы хуже, чем сама проблема.
    • 0
      Это вроде как такой кодстайл в серверном JS используется. Во многих модулях для Node.js такое видел.
      • 0
        В самом node.js код написан без этих выкрутасов.

        Единого стиля кода, с которым согласны все в js, все равно нет. Отступы — то табы, то 4 пробела, то 2 пробела; точка с запятой — то ставится, то не ставится; запятая — то в начале строк, то в конце, и т.д. Хорошо хоть с camelCase определились.
        • 0
          Единого стиля нет ни в одном языке. jslint.com + [The Good Parts Mode] хороший тон, дня JavaScript
          • 0
            pep-8?
            в питоне сразу заклюют, если не по pep-8 код. Поэтому все пишут по pep-8 и проблем не имеют. Не так ведь важно, какие именно соглашения, главное — чтобы они соблюдались, а в js этого нет.
            • 0
              Кто это все? Угу, даже стандартная библиотека не придерживается например такого из pep-8:

              Limit all lines to a maximum of 79 characters.

              • 0
                Там вступление есть о том, как pep-08 применять (A Foolish Consistency is the Hobgoblin of Little Minds).
      • 0
        Нене, добавить новый элемент проще (запятая + элемент в 1 строчку). Кодогенераторам проще.
        • 0
          как вы ласково отозвались о программистах, употребляющих этот стиль)

          Странно, что этот стиль вообще употребляют в серверном js. V8 завершающую запятую игнорирует, вот это работает (только что проверил):
          var arr = [
             'vasia',
             'petya',
          ];
          

          Может, чего-то я тут не понимаю, но это уж получше читается.
          • 0
            Бегом показывать этот массив IE =)
            • +1
              Снимаю шляпу. Работает =(
              • +1
                в IE6 и IE7 не работает.

                Но этот синтаксис (запятая после последнего элемента) не запрещен в ECMAScript 3 и явно разрешен в ECMAScript 5.

                Мне кажется, что такие странные приемы для обхода багов IE на сервере под V8 смотрятся диковато и бессмысленно.
                • +1
                  В случае расшаренного кода, который испольуется и в браузезе и на сервере, смысл есть.
            • 0
              Поставить IE на linux-сервер без иксов и запустить его как интерпретатор js из программы на node — задача, прямо скажем, нетривиальная.
              • 0
                Всё работает. Попутал я с хеш массивом, где запятая критична.
                • 0
                  длину массива сравните в ие и в других браузерах, вроде бы в ие должно быть побольше…
            • 0
              Работает, но добавляет лишний null-элемент в конце массива.
              Что еще хуже ибо может совершенно неожиданно вылезти боком.
              Не работало бы, если бы это был литерал объекта.
              • 0
                Проверил сейчас на node/V8, длина массива не меняется от добавления запятой, и при создании объекта ( var dict = {'foo': '1', 'bar': '2',}) никаких ошибок не вываливается, объект создается правильно. Вы точно не поведение IE описываете?
                • 0
                  Я точно поведение IE описываю :-D
                  Про специфику V8 вообще не очень в курсе.
              • 0
                Написал камент об этом ниже, не прочитав сначала ваш.
                Так вот, по-моему такое поведение логично.
          • 0
            по-моему, такая конструкция должна работать и быть эквивалентна
            var arr = [
               'vasia',
               'petya',
               null
            ];
            
            
            • 0
              var a = [ «a», «b», ];
              a.length // 2

              var a = [ «a», «b», null ];
              a.length // 3
    • +2
      Сразу видно, что строка не является самостоятельной. И операторы "+", "-" тоже лучше писать с новой стороки, что бы четко видеть, что это просто продолжение предыдущей строки.
      • +1
        Мне думается, несамостоятельность строки можно указать отступом.
      • 0
        А вот с операторами — это не для javascript совет. Пример:
        function f() {
        //…
        return
        -42;
        }

        Хотя в некоторых других языках известны личности, которые все пунктуационные операторы на начало новой строки переносят.
        • 0
          имелись ввиду не унарные операторы, а разрыв длинных формул и конкатенаций на несколько строк.
    • 0
      У этого стиля много плюсов. Во-первых, автоматом не поставишь лишнюю запятую в конце, во-вторых, можно спокойно комментировать такие строчки, опять же не думая, что если закомментировал последнюю, то надо убрать запятую у предыдущей.

      ЗЫЖ ну а что неприятно… — это дело привычки.
    • +2
      Вы наверно ещё не видели такое:
      var a = 1
         ,b = 2
         ,c = 3
         ,d = 4
         ;
      
      • 0
        Ага, это я и имел в виду. Встретил подобное в исходном коде фреймворка express для node.js. Ранее автор грешил как раз опусканием точки с запятой везде, где только можно, сейчас отказался от затеи. Надеюсь, и эта мода пройдет.
        • +3
          Не привычно, но следует сказать спасибо TJ Holowaychuk за express пусть и с таким кодом, похожим на гребенку. Я рад, что он уходит от плохого.
          Ещё очень часть переносят на новую строку метод в цепочках вызовов. И опять же ставят точку с запятой в конец, чтобы было удобнее добавлять новый вызов в цепочку. С этим подходом я полностью согласен.
          $('#button')
          .css('color', 'red')
          .click(onbuttonClick)
          .animate({width: '150px'})
          ;
          
    • 0
  • +9
    Как по мне — слишком много буков.

    Проще, надежнее и понятнее ставить; всегда по окончании оператора, как в C/С++/С#, Java. Автовставка от лукавого.
    • +1
      Статья призывает преодолеть ложное чувство простоты, надёжности и понятности в случаях типа
      return
         (a + b);
      


      ибо здесь точка с запятой лишь создаст впечатление, что всё правильно.
  • +7
    Как сторонник правила «явное лучше неявного», считю, что точку с запятой надо ставить всегда. К чему лишние трудности в чтении и понимании?
    • +1
      В случае с точкой с запятой — согласен полностью. А вот насчет правила «явное лучше неявного» — не всегда. Зависит от того, насколько однозначно можно трактовать «неявное» исходя из контекста. Если однозначно — лучше пусть будет неявно. Это касается всех случаев использования «Conventions», тех самых, которые «over Configuration».
    • 0
      категорически согласен.
  • +3
    Я привык писать на JS так же как на C++, поэтому о некоторых тонкостях не знал, если честно, но и не встречал на практике. Хотя статья интересная, но уж больно длинная…
  • +1
    Присоединяясь к двум предыдущим комментаторам добавлю что линтеры (jslint, google linter) помогут не забыть это делать всегда.
    • 0
      jslint в свое время заставил изучить эту тему
  • +2
    парсится как три пустых оператора, и выполняет ничего три раза

    улыбнуло)
  • 0
    Если разрабатываешь на каком-нить языке программирования (к примеру PHP) то проще следовать основному языку, т.к. так привычней и глаз не режет отличия в окончании строк.
    • +1
      Согласен. У меня основной руби, и поэтому мне режут глаз точки с запятой. Что же касается компрессоров, то статья высказывается в том смысле, что компрессоры, которые не могут осилить валидный код, хоть и без ТЗ, нужно выкинуть. Ибо неизвестно, где ещё они накосячат.
      • +1
        Лишняя точка с запятой и все фигурные скобки — хороший тон для JavaScript. Я представляю как не ловко программистам Python видеть такое
        var a = {
           b: function () {
               if () {
        
               }
           }
        }
        
        Их наверно одна конструкция function (){if(){}} сводит с ума :) Все современные компрессоры заставляют программистов писать хороший JavaScript код: ставить везде; и везде использовать {}. Так что не нужно винить компрессоры они делают хорошую работу. Я понимаю вашу привычку, но и вы уважайте этот дополнительный язык, его программистов, которые возможно будут читать ваш код и его тон хороших скриптов.
        • 0
          Как показал комментарий habrahabr.ru/blogs/javascript/111563/#comment_3560507 не все компрессоры заставляют писать хороший код
        • 0
          Эээ… а при чём тут питон? Я очень уважаю js, и не считаю его дополнительным, и кода пишу не меньше, чем на руби. Вышеприведённый фрагмент не содержит ни одной точки с запятой, и никак не противоречит моим принципам, и ничего дикого я не вижу. Ну разве что не скомплируется с пустым условием =)

          Нет никакой разницы, говорим ли мы «точка с запятой необязательна, почти всегда парсер её вставит», как в js, или же «точка с запятой не нужна, но в редких случаях нужно '\' для продолжения строк», как в руби, это всего лишь дело устоявшихся традиций, основанных, не в последнюю очередь, на неинформированности.

          Если я пишу для себя, я ТЗ не ставлю, наряду с другими своими правилами оформления кода, и мне все равно, кто что про это думает, никакого неуважения ни к кому я не выказываю, и не считаю что хороший тон это «точки с запятой всегда». Вероятность того, что мой код будет читать кто-то, отличный от меня, довольно низка, а того, что кто-то неотличный, напротив того, очень высока, так что приоритеты соответствующие.
          • 0
            Программисты Python ненавидят лишние скобки (в Ruby, на сколько я знаю, тоже не одобряют стиль со скобками), поэтому и привел такой пример. И поэтому зачастую норовят написать вот так if(a) a=100 else b=200, что очень напрягает меня и многих JavaScript программистов. Многие вешают на JavaScript тег «Дополнительный язык» — могу писать как хочу. У вас хороший подход к языкам, комментарий полностью разъяснил мне вашу позицию.
            • 0
              Программисты на Python не только не любят лишние скобки, но и лишние уровни вложенности.
              «Плоское лучше, чем вложенное»

              def test(a):
              >>if type(a)!=str:
              >>>>return False
              >>if a[0]=='0':
              >>>>return False
              >>return a[::-1]

              и

              def test(a):
              >>if type(a)==str:
              >>>>if a[0]!='0':
              >>>>>>return a[::-1]
              >>return False

              В первом случае код длиннее, но зато основная работа функции на виду. то есть, в начале мы явно указали, в каких случаях функция не работает с объектом, а потом без лишней вложенности описали, что делается, если объект нам подходит. Во втором же случае основная работа с объектом на два уровня глубже, что делает код трудночитаемым.
              • 0
                без лишней вложенности. короче и понятнее вот такой вариант
                def test( a ):
                    if type( a ) != str or a[ 0 ] == '0' :
                        return False
                    return a[::-1]

                и если появятся еще «исключительные условия», то их стоит добавить в первое условие.
                читабельность от этого не пострадает. в крайнем случае если условия очень запутанные и хитрые, то разбить их на несколько строк, примерно вот так:
                def test( a ):
                    if a is None \
                        or type( a ) != str \
                        or a[ 0 ] == '0' :
                        return False
                    return a[::-1]
        • 0
          Надо бы точку с запятой в конце поставить… после последней фигурной… для порядку. ;-)
    • +2
      Глупости. Если разрабатываешь на каком-нить языке программирования (к примеру PHP) не надо тащить его в другой язык программирования. Следует понимать, что ты делаешь или отдать тому, кто понимает.
      • 0
        А при чем тут тащить язык? Я вполне понимаю и PHP и JavaScript, но мне привычней ставить ТЗ, вот и все…
  • +6
    Вот прочитал же, что ТЗ тут «точка с запятой». А при чтении упорно всплывает «Техническое Задание». Не плодите сущностей…
    • +1
      Поддерживаю. В исходном тексте никаких сокращений нет, а переводчик ввел новую аббревиатуру, которая к тому же имеет в нашем языке устойчивое значение. Следовало использовать конструкцию «;» или писать полностью.
      • 0
        И ведь ещё и приживётся этакий новый смысл, чего доброго.

        (Кстати, на «ТСЗ» также нельзя исправить это, потому что «ТСЗ» является общепринятым сокращением словосочетания «товарищество собственников жилья».)
        • +1
          «Зилья»?
      • +1
        Можно «тчк зпт» или без пробела «тчкзпт», в телеграфном стиле пишут так:

        ИЗ ЧИСЛА ЛАУРЕАТОВ СООТВЕТСТВУЮЩИХ ПРЕМИЙ ПРАВО НА ДОПОЛНИТЕЛЬНОЕ МАТЕРИАЛЬНОЕ ОБЕСПЕЧЕНИЕ ИМЕЮТ двтч
        ЛАУРЕАТЫ СТАЛИНСКОЙ ПРЕМИИ тчк зпт
        ЛАУРЕАТЫ ЛЕНИНСКОЙ ПРЕМИИ тчк зпт
  • 0
    спасибо, очень хорошая статья — всё по делу
  • +1
    за
    i
    ++
    j
    

    и д.р. выкрутасы надо по рукам бить
    • +1
      Вот если был бы JavaScript трибунал, то бы за ваш всего лишь посадили в карцер
      А вот, думаю, за такой минимальное наказание — расстрел на месте:
      b=(a=[0,1,2][0,1,2])?
      !!~~++a+1..toString
      (void(a))
      :(c=eval)
      ('alert("42")');

      Да, и это код прекрасно работает. Кто-нибудь скажет сходу, что будет в b? :)
      • 0
        console.log(b); ( и да — с ТЗ в конце :) ) спасёт отца русской демократии на просторах Яваскриптовых поделок
      • +1
        такс. ну a будет равно [0,1,2][2], следовательно 2, следовательно правый код тернарного оператора (alert("42")) не выполнится. разбираем левую часть. в toString аргументы никакие не передаются, потому void(a) это просто шум. ~~ равносильно parseInt. 1. равносильно 1.0 или (1).
        Следовательно, принимаем одно из следующих выражений:
        !!(parseInt(++a + 1).toString());
        (!!parseInt(++a)) + ((1).toString());
        !!(parseInt(++a) + ((1).toString()));
        !!parseInt(++a + (1).toString());
        

        Я в порядке выполнения этих операндов плаваю, на самом деле, потому всегда стараюсь ставить скобочки(

        Все, кроме второго выражения возвращают true, второе — «true1». Правильный ответ «true1», но это я уже посмотрел в файрбаге:

        (!!parseInt(++a)) + ((1).toString());
        


        Хотя я не верю в реальность такого кода )
        • 0
          ~~ не равносильно parseInt, а так всё правильно.
          • 0
            ну да, ~~'08' и parseInt('08') вернут разный результат, но в данном случае они равносильны
            • 0
              В данном случае ~~ вообще бессмысленнен, но самое главное, что
              parseInt("12px") → 12
              ~~"12px" → NaN
              
              var o = {toString: function(){return "5";}, valueOf: function(){return 3;}};
              parseInt(o) → 5
              ~~o → 3

              Я просто придираюсь к словам. Уверен, что вы понимаете разницу, но не нужно смущать тех, что не понимает.
              • 0
                Ок =)
    • 0
      ой, а по рукам-ли? :)
  • +2
    >Всё, что надо знать о точке с запятой
    >СТЕНА ТЕКСТА
    >СТЕНА ТЕКСТА
    >СТЕНА ТЕКСТА
    >СТЕНА ТЕКСТА
    >СТЕНА ТЕКСТА
    >Перевод
    мда. им там на западе писать то больше не о чем?
    вижу статью активно плюсуют. за что? по-моему у большинства хабраюзеров уже рефлекс — СТЕНА ТЕКСТА, значит надо плюсануть.
    давайте запостим вывод компиляции ядра линукса. ну, вывод make.
    там будет стена текста раз в 20 а то и 50 больше, значит получим 100-200 плюсиков.
    только не забыть вставить красивую картинку с с*ськами до ката.
    • +2
      Как заметил у них там на западе бывший президент Франции Миттеран, вы только что «упустили великолепную возможность промолчать» ;)
    • +2
      правильный у вас юзерпик
  • 0
    ТСЗ не обятательна в конце файла, да, но на практике это приводит к очень сложноловимой ошибке, когда все файлы конкатенируются вместею получается
    Получается
    1.js
    var one = {}
    2.js
    var two = 1
    А после конкатенации
    var one = {}var two = 1
    Если конкатенация проводиться только на production сервере — то такую ошибку очень тяжело ловить.
    Поэтому зачастую у опытных программистов можно встретить ТСЗ первым символом в файле — так они защищаются от подобных ситуаций, потому что зачастую не знаешь что за файл будет перед твоим при конкатенации.
    • 0
      Конкатенировать файлы через пробел — допускать нарушение спецификации ECMAScript. Явно сказано, что перед концом файла парсером автовставляется точка с запятой.

      Ну и кроме того, «хороший тон» иметь в конце файла всё же CR.
      • 0
        Поскольку для конкатенации нет стандартных решений — то каждый реализовывает по-своему. Соответственно не зная написанного выше можно напороться на очень нетривиальный и сложноловимый баг.
  • 0

    Я тут проводил раскопки и обнаружил странный артефакт:


    a = b + c
    ;(d + e).print()

    Не надо так писать, в JS есть специально предназначенный для этого оператор:


    a = b + c
    void (d + e).print()

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.