За последние несколько месяцев я сделал несколько доработок для JSHint, в основном с целью изучить ES6 (я особенно горжусь тем, как переделано обнаружение областей видимости для переменных). Во время этого процесса я наткнулся на несколько вещей, которые меня удивили — в основном, в ES6, однако есть и кое-что про ES3, что я до этого никогда не использовал.

Break из любого блока


Наверняка вы знаете, что в любом цикле можно использовать ключевые слова break и continue — это стандартная возможность в современных языках программирования. Однако не все знают, что циклам можно давать метки и с их помощью прерывать любой конкретный цикл:

outer: for(var i = 0; i < 4; i++) {
    while(true) {
        continue outer;
    }
}

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

switch(i) {
   case 1:
       break;
}

Вообще говоря, именно поэтому Крокфорд не советует добавлять отступы перед case — выражение break выкидывает из блока switch, а не case, но мне вариант с отступами кажется более читабельным. Выражения switch также можно помечать меткой:

myswitch: switch(i) {
   case 1:
       break myswitch;
}

Также можно объявлять блоки просто так. Знаю, что это также доступно в C#, и наверняка в других языках тоже:

{
  {
      console.log("Я внутри произвольного блока");
  }
}

Если сложить все это вместе, можно выйти из любого блока с помощью метки:

outer: {
  inner: {
      if (true) {
        break outer;
      }
  }
  console.log("Эта строчка никогда не выполнится");
}

Разумеется, это относится только к break — оператор continue допустим только внутри цикла. Я ни разу не видел метки в коде на Javascript — скорее всего, потому, что если вдруг понадобится экстренно выйти из более чем одного блока, это повод переписать код на функцию с return.

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

function(a, b, c) {
  if (a) {
     if (b) {
       return true;
     }
     doSomething();
     if (c) {
       return c;
     }
  }
  return b;
}

Добавляем метки, и получается вот что:

function(a, b, c) {
  var returnValue = b;
  myBlock: if (a) {
     if (b) {
       returnValue = true;
       break myBlock;
     }
     doSomething();
     if (c) {
       returnValue = c;
     }
  }
  return returnValue;
}

Или же, можно было бы использовать больше блоков:

function(a, b, c) {
  var returnValue = b;
  if (a) {
     if (b) {
       returnValue = true;
     } else {
       doSomething();
       if (c) {
         returnValue = c;
       }
    }
  }
  return returnValue;
}

Вообще, вариант с метками мне нравится меньше всех, но может только потому, что я к нему не привык?

Деструктуризация существующей переменной


Сперва — фишка, которую я не могу могу объяснить. В ES3, судя по всему, можно добавить скобки вокруг переменной при присваивании и это будет работать:

var a;
(a) = 1;
assertTrue(a === 1);

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

Деструктуризация — это процесс получения значения переменной из объекта или массива. Чаще всего можно видеть подобный пример:

function pullOutInParams({a}, [b]) {
  console.log(a, b);
}
function pullOutInLet(obj, arr) {
  let {a} = obj;
  let [b] = arr;
  console.log(a, b);
}
pullOutInParams({a: "Hello" }, ["World"]);
pullOutInLet({a: "Hello" }, ["World"]);

Но можно сделать то же самое и без let, var и const. Для массива достаточно написать вот так:

var a;
[a] = array;

А вот с объектом не получится — его необходимо обернуть в круглые скобки:

var a;
({a} = array);

Причина в том, что это дает слишком большой простор для двусмысленного толкования и ошибок, связанных с анонимными блоками кода, потому как автоматическая расстановка точек с запятой превращает идентификаторы в вычисляемые выражения, а они могут иметь побочные эффекты:

var a = {
   get b() {
     console.log("Превед!");
   }
};
with(a) {
  {
    b
  }
}

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

var a, b, c;
(a) = 1;
[b] = [2];
({c} = { c : 3 });

Деструктуризация с числами


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

var {1 : a} = { 1: true };

Или строки в кавычках:

var {"1" : a} = { "1": true };

А еще можно вычислять имя свойства из выражения:

var myProp = "1";
var {[myProp] : a} = { [myProp]: true };

Это позволяет с легкостью написать очень запутанный код:

var a = "a";
var {[a] : [a]} = { a: [a] };

Объявления класса привязаны к блоку


Объявления функции поднимаются в самый верх блока, что позволяет использовать их до объявления:

func();
function func() {
  console.log("Все в порядке");
}

А вот если функция объявляется в ходе присваивания переменной, то поднимается только объявление переменной, но не присваивание ей значения:

func(); // func объявлена, но не имеет значения, поэтому ошибка "func не является функцией"
var func = function func() {
  console.log("Всё в порядке");
};

Классы — одна из наиболее популярных частей спецификации ES6, и всегда считались своего рода синтаксическим сахаром для функций. Однако если вы думаете, что этот код заработает, то вы ошибаетесь:

new func();

class func {
  constructor() {
    console.log("Все в порядке");
  }
}

Несмотря на сходство с первым примером, оно не работает. На самом деле это эквивалент следующего кода:

new func();

let func = function func() {
  console.log("Fine");
}

Тут мы пытаемся обратиться к func внутри временной мертвой зоны, что является синтаксической ошибкой.

Параметры-тёзки


Я предполагал, что у функции не может быть двух параметров с одним и тем же именем — а на самом деле может!

function func(a, a) {
  console.log(a);
}

func("Привет", "Мир");
// выводит "Мир"

Однако в strict mode всё не так:

function func(a, a) {
  "use strict";
  console.log(a);
}

func("Привет", "Мир");
// в Chrome будет ошибка - SyntaxError: Strict mode function may not have duplicate parameter names

Оператор typeof небезопасен


Ладно, ладно, я украл это наблюдение, но повторить все равно будет не лишним.

До ES6 было широко известно, что с помощью оператора typeof можно безопасно узнать, объявлен ли идентификатор, даже если ему не присвоено значение:

if (typeof Symbol !== "undefined") {
  // Symbol доступен
}
// Этот код выкинет исключение, если Symbol не объявлен
if (Symbol !== "undefined") {
}

Но теперь это работает только в том случае, если вы не объявили переменную с помощью let или const. Всему виной ВМЗ, из-за которой обращение к переменной до ее присваивания является синтаксической ошибкой, даже несмотря на то, что «под капотом» объявление переменной все равно поднимается в самый верх блока.

if (typeof Symbol !== "undefined") {
  // Symbol доступен
}
let Symbol = true; // вызывает синтаксическую ошибку в условии выше!

Создание массива


Я всегда избегал создания массива с помощью ключевого слова new. В основном потому, что аргументы могут быть либо длиной массива, либо его элементами:

new Array(1); // [undefined]
new Array(1, 2); // [1, 2]

Однако коллега недавно наткнулся на кое-что, что мне раньше не встречалось:

var arr = new Array(10);
for(var i = 0; i < arr.length; i++) {
  arr[i] = i;
}
console.dir(arr);

Этот код выдает массив с числами от 0 до 9. А что будет, если отрефакторить его с использованием map?

var arr = new Array(10);
arr = arr.map(function(item, index) { return index; });
console.dir(arr);

Массив остался неизменным. Судя по всему, конструктор, принимающий длину, создает массив и задает свойство length, но не создает никаких элементов. Поэтому обратиться к свойству можно, а перечислить элементы нельзя. А если задать значение какому-нибудь элементу?

var arr = new Array(10);
arr[8] = undefined;
arr = arr.map(function(item, index) { return index; });
console.dir(arr);

Получаем массив, где восьмому элементу присвоено число 8, но все остальные значения не заданы. Если посмотреть на код полифилла для функции map, она проверяет заданность свойства с помощью оператора in. Такого же поведения можно достичь с помощью литералов массива:

var arr = [];
arr[9] = undefined;
// или же
var arr = [];
arr.length = 10;

Другие жемчужины


В блоге разработчиков Mozilla есть отличная статья про функции со стрелками, где говорится о том, что комментарии можно помечать символом <--. Неплохо почитать и остальные посты в блоге.

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


  1. xmoonlight
    04.07.2015 02:15
    -22

    Огромное Спасибо за чёткое и внятное изложение ценного материала!
    Ваша публикация и рефакторинг кода: наш путь к быстрым, стабильным и нетребовательным к ресурсам приложениям.
    Спасибо!


    1. xmoonlight
      05.07.2015 19:20

      Коллеги, откуда столько негодования ?!
      Хотелось бы узнать…


      1. eyeofhell
        06.07.2015 09:33
        +1

        Внешне очень похоже на копирайтинг. Поисковые движки копирайтинг любят, хабр — нет ^_^.
        К прмеру, если бы вы написали «спасибо, узнал много нового» — никто бы не проголосовал или слабый плюс. А у вас хорошая такая фраза… По всем правилам, даже призыв к действию есть :).


        1. xmoonlight
          07.07.2015 03:33

          Понятно. Слегка ошибся с аудиторией… :)
          Однако, я многого еще не знаю! :)
          Спасибо за статью и пояснения eyeofhell'у


          1. TheShock
            07.07.2015 14:53
            +6

            «Спасибо за статью и пояснения eyeofhell'у, купить ковры за смс недорого»

            Извините, просто у вас очень прикольный стиль изложения)


  1. gonzazoid
    04.07.2015 02:45
    +49

    ммм. Я так понимаю, стрелять в ногу в одиночестве Вам надоело, и Вы решили набрать народ в клуб, заодно научив их отстреливать себе руки. Поддерживаю, зачотно!


  1. xGromMx
    04.07.2015 03:10
    +1

    Прикольно применять вот так:

    var x, y, z;
    
    z = (x = 10, y = x + 20, x * y); 
    


    Последняя запичсь запишется в результат z


    1. Keyten
      04.07.2015 11:26

      Помню похожее — обмен переменных значениями без дополнительной переменной.

      var a = 5, b = 10;
      
      a = [b, b = a][0];
      


      1. Apx
        04.07.2015 11:35
        +2

        Имхо это не так. Доп. Переменная тут есть в виде инлайн массива. Без доп переменных свап чисел делается математическими операциями))


        1. VolCh
          04.07.2015 15:05
          +2

          Переменная — это именованная область памяти, доступная для изменения по имени, а не любая


          1. Apx
            04.07.2015 16:42
            -1

            Терминология это кончено классно, но факт останется фактом. Память выделена, и до конца окончания блока или запуска gc память не очистится.


            1. mayorovp
              06.07.2015 11:51
              +2

              То, что делается мат. операциями — называется «обмен значений без дополнительной памяти». А в задаче «обмен значений без дополнительной переменной» использовать доп. память, если она не именованная — можно!


        1. xGromMx
          04.07.2015 16:23
          +1

          Можно вот так a = b + (b=a, 0) или так a = (a = a ^ b, b = a ^ b, a ^ b) или сокращенно a ^= (b ^ (b=a))


          1. xGromMx
            04.07.2015 22:03
            -3

            За что минус?


            1. dima_mendeleev
              05.07.2015 12:31

              видимо какому-то гуманитарию знаки пунктуации не понравились


  1. Zibx
    04.07.2015 03:27
    +10

    Вот таким образом можно получить массив от 0 до 9 во славу сатане:

    (new Array(10+1))
        .join(' ')
        .split('')
        .map(function(el,i){ return i; })
    


    1. asdf404
      04.07.2015 06:38
      +2

      Можно ещё вот так:

      Array
        .apply(null, new Array(10))
        .map((_, i) => i);
      


      1. Aingis
        04.07.2015 12:39
        +4

        Если уж говорить о ES6, то там есть два варианта попроще:

        [...new Array(10)].map((i, n) => n);

        Array.from(new Array(10), (i, n) => n);


  1. StreetStrider
    04.07.2015 03:31

    А что будет, если отрефакторить его с использованием map? Массив остался неизменным.
    Да, есть такое. filter-map-reduce семейство функций на разрежённых массивах не работает с пропусками. Некоторые библиотеки в угоду производительности даже исключают разрежённые массивы из области своего применения.


  1. Ramzeska
    04.07.2015 03:45
    +25

    Кажется ребята ошиблись — надо было слово break заменить на goto и холивар был бы обеспечен )


    1. impwx Автор
      04.07.2015 10:33
      +2

      Тогда уж скорее gofrom: тут мы не переходим на метку, а наоборот, уходим от нее подальше.


      1. lany
        04.07.2015 16:16
        +1

        Вообще-то есть COMEFROM :-)


    1. konsoletyper
      04.07.2015 11:28
      +1

      Кстати, у break на самом деле гораздо более скудные возможности по сравнению с goto. Например, нельзя «прыгнуть» внутрь цикла, да и вообще нельзя таким образом создать цикл. Это исключает появление такой неприятной для разработчиков компиляторов штуки, как irreducible flow graph.

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


  1. Drag13
    04.07.2015 08:34
    -2

    label: 1;
    goto: 1;
    Кто то помнит игру goto на паскале?


  1. eyeofhell
    04.07.2015 09:05
    +3

    По поводу добавления скобок, предполагаю что тут все просто — скобки это по сути оператор, который превращает то что внутри них в один expression. Если мы берем литерал «a» слева от оператора "=", то сам по себе литерал будет «lvalue expression». Заключаем его в скобки — снова получаем «lvalue expression». Насколько я помню, в скобки можно заключать иного всего без побочных эффектов.


    1. impwx Автор
      04.07.2015 10:35

      Это отвечает на вопрос «как так случилось», но на вопрос «зачем так сделали» ответа, судя по всему, нет — скобки могут быть побочным эффектом того, как была написана грамматика.


      1. eyeofhell
        04.07.2015 10:40
        +1

        Скобки — универсальный механизм. Подозреваю, что данный конкретный случай вообще не представлен в грамматике и является следствием универсального механизма скобок. Не отключать же его для такого случая? :)


        1. impwx Автор
          04.07.2015 10:49
          -1

          Практически уверен, что так и есть. Однако это индикатор того, что грамматика могла бы быть составлена лучше — у lvalue обычно прописываются жесткие рамки.


          1. eyeofhell
            04.07.2015 10:54

            Подозреваю, что это для большинства языков со скобочками так работает. На вскидку, c:

            int main(int argc, char** argv) {
              int a = 0;
              (a) = 1;
              return 0;
            }
            


            Python:

            a = 1
            (a) = 2
            


            1. impwx Автор
              04.07.2015 11:12

              Признаю, ошибся. Так действительно сделано во многих языках. Но менее странным оно от этого не выглядит :)


          1. Apx
            05.07.2015 02:05

            Скобки в грамматике в таких конструкциях как раз отвечают за order of operation. Лучше составить грамматику я думаю было бы более больно для синтаксиса. Т.к. под каждый случай прийдётся писать доп. правила для скобок.


            1. impwx Автор
              05.07.2015 10:10

              Когда я составлял грамматику для своего языка, было просто три правила для присваивания. В упрощенном виде они выглядят так:

              assign_identifier = identifier "=" expr
              assign_member = expr "." identifier "=" expr
              assign_index = expr "[" expr "]" "=" expr
              

              Скобки могут быть быть слева от знака присваивания, но только в подвыражении, типа (a + b).pty = 1.


              1. Apx
                05.07.2015 14:14

                Я после университета конечно благополучно забыл как это правильно делается точно, но хорошо помню, что когда мы с одногруппником рожали свои грамматики, то долго мучались именно с order of operation и скобками. Конечно не могу сказать с уверенностью, но помоему скобки должны всё же учавствовать более одного раза.


                1. impwx Автор
                  05.07.2015 16:02

                  Если интересно, в статье про написание парсера я предлагал довольно удобный способ описания order of operation в грамматике (правда, на сишарпе, но алгоритм применим к любому языку).


                  1. Apx
                    05.07.2015 16:52

                    Читал в своё время, помню :)


  1. Alexc5c5c5
    04.07.2015 10:17

    Break на метку, очень напоминает goto… Не надо так


    1. vsb
      04.07.2015 10:41
      +2

      break, continue, return это и есть частные случаи goto.


    1. vintage
      04.07.2015 10:47
      +5

      Это переход не к метке, А к концу помеченного блока. Например, очень полезен в подобной логике:

      checking: {
          while( x --> 0 ) {
              if( x % 10 === 0 ) break checking
          }
          throw Error( 'Чисел кратных 10 не найдено' )
      }
      


  1. Keyten
    04.07.2015 11:30
    +4

    А ещё можно исполнять код после return-а. С помощью try-finally. Не только в JS, кстати.

    function test(){
        // do something
        try {
            return true;
        }
        finally {
            console.log('Hey! :)');
            return false;
        }
    }
    


    1. impwx Автор
      04.07.2015 12:10
      +1

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


  1. gibson_dev
    04.07.2015 12:02
    -3

    А можно просто было прочитать документацию, не?


    1. forgotten
      04.07.2015 15:44
      +2

      Удачи. 574 страницы увлекательного чтения ждут вас.
      wiki.ecmascript.org/lib/exe/fetch.php?id=harmony%3Aspecification_drafts&cache=cache&media=harmony:ecma-262_6th_edition_final_draft_-04-14-15.pdf


      1. ilmirus
        04.07.2015 17:27

        Знаете, мне довелось читать много стандартов языков программирования (С, С++, Fortran, EcmaScript, OpenMP, OpenACC, Posix). Из них ES 5.1 самый, имхо, простой для прочтения и (тут никакого сарказма) увлекательный. Поэтому я, не написавший ни одной продашн строчки на JS, с недоумением читал статью. Какого, простите, хрена нарушать нулевое правило программирования (RTFM)? Поэтому ваш сарказм здесь неуместен. И меня поражает сообщество, которое минусует в ответ на RTFM.

        Из того, что мне запомнилось из работы над Jerry (https://github.com/Samsung/jerryscript) (Скажу честно — до сих пор жалею, что согласился на уговоры присоединиться к разработке и всей душой желаю смерти этому монстру):
        1) BlockStatement очень похож на ObjectLiteral (да-да, тот самый WAT {} + [] !== [] + {})
        2) Keywords не являются ключевыми словами, если используются в качестве ключей ObjectLiteral (PropertyName). Например, var a = {for: 1, var: 2}. Более того, следующая конструкция также валидна: a.for + a.var. Вот это действительно WAT! Keyword не является keyword!
        3) Ну и конечно hoisting добавил мне пару седых волос на моей юной голове.


        1. forgotten
          04.07.2015 21:44
          +7

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

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

          Разумеется, за несколько лет работы тонкости языка всё равно более или менее выучиваются, но это не в коем случае не самоцель и не показатель скилла.


          1. ilmirus
            05.07.2015 15:04

            Вот тут вы неправы. Хотя я являюсь начинающим программистом, я не считаю, что скилл программиста зависит от знания языка программирования, даже если это С++, тем более если это С++. Или от знания алгоритмов. Это все базовые знания, которые программист усваивает в ВУЗе, до начала своей профессиональной карьеры. И даже при смене языка, хотя бы структуру спецификации или стандарта _крайне_ желательно усвоить, чтобы в случае чего подглядеть, что там да как.
            И я ни слова не сказал о скиллах, я лишь говорил о том, что документацию надо знать (вне зависимости от скиллов), по ней надо уметь искать, её надо перечитывать время от времени, в конце концов. Только для того, чтобы не удивляться, когда замена function foo(){} на var foo = function(){} ломает скрипт. Или перенос выражения после return на новую строку ломает скрипт. Или [выберите свой любимый способ выстрелить в ногу]
            И да, для тех, кто не знает языка вероятного противника, доступен перевод (про качество перевода не скажу ничего, ибо не читал) es5.javascript.ru Правда, ES6, насколько я знаю, не переведен.
            Кроме того, как я уже говорил, стандарт ES очень хорошо написан. Простым и последовательным языком. Прочесть его можно за неделю по вечерам и выходным. Понять? Ну, поэтому я и говорю «перечитывать время от времени». Сколько времени это сыкономит на отладке «за несколько лет работы»? Я не знаю, ибо «документацию надо знать». И если какая-то фигна описана в документации, а программист ее не знает, то ему нужно сказать RTFM и, желательно, раздел, где эта фигня описана.
            Но это не всегда спасает от нубских ошибок. Например, попытка изменения строкового литерала, который находится в .rodata. Куда копать при SIGSEGV'e? Ясное дело, надо программисту обьяснить, что такое ELF, что такое Special Segments, что такое .rodata и кинуть в него ссылкой на ELF Format Specification. Ибо не все описано в стандарте С.
            Вот что меня поражает, так это то, что, имхо, нормальная ситуация, когда сишники и плюсовики у нас на работе (а других у нас и не водится) свободно объяснят тонкости языка (например, integer promotion (да, это базовое знание С, но никто не гонит ссанными тряпками)), часто с ссылками на разделы стандарта, по вашему мнению, просто немыслима. Это не наезд, а опять же отсылка к RTFM.

            Да, я допускаю, что это во мне говорит юношеский максимализм, помноженный на профессиональную деформацию. Мне просто по работе нужно знать и изучать кучу всего, начиная от языка, на котором я программирую, продолжая через знания конкретной архитектуры процессора, для которой моя программа генерирует код (это, естественно, включает в себя знания ассемблера), а также языка программирования, с которого моя программа генерирует код. Кроме того, без знания ядра и системных вызовов мне нечего делать. И я не говорю о оптимизациях, которые, к слову, совсем не просты, хотя и обязательны, ибо «компилятор должен в первую очередь генерировать коректный код, а во вторую — эффективный». И для всего этого есть документация, и чем она полнее, тем лучше для меня. Поэтому стандарты и спецификации для меня на вес золота. И, честно говоря, я обожаю читать и перечитывать плюсовый стандарт перед сном. Так засыпается лучше. Правда, иногда кошмары снятся, но это ничего, терпимо.


            1. eyeofhell
              06.07.2015 09:42

              Оффтопик. Если не секрет, чем вы таким страшным занимаетесь, что одновременно:

              Знаете, мне довелось читать много стандартов языков программирования (С, С++, Fortran, EcmaScript, OpenMP, OpenACC, Posix).

              Хотя я являюсь начинающим программистом


              Помню, много лет назад, когда я уже писал на плюсах больше десяти лет, столкнулся в чистом поле с особо заковыристым шаблоном. И нужно было понять — оно так по стандарту работает, или это фича реализации Microsoft Visual C++ 6.0

              Из 1400 страниц стандарта я за несколько дней изучил 50. И не могу сказать, что это было как-то очень уж легко. Несмотря на то, что сами плюсы я знал довольно неплохо.

              Конечно, я не самый талантливый программист, но чтобы внимательно прочитать и понять все 1400 страниц плюсового стандарта… Это не на пару дней занятие. И боюсь, даже не на месяц. Хмурая штука.


              1. ilmirus
                07.07.2015 01:59
                +1

                Довелось читать != прочел целиком и освоил. Сегодня одно, завтра другое. За последние три года прочел только две трети плюсового стандарта (в отличие от того же ES, который я полностью прочел и осознал где-то за пол-года, так как меня заставили^W^W мне нужно было его реализовать). Многие вещи из стандартной библиотеки я даже не трогал. Как понадобится — выделю время. А понадобится либо при затыке, либо при необходимости лезть в компилятор плюсов/стандартную библиотеку. Однако, структура документа мне понятна, к языку (не программирования, а документа) я уже более-менее привык, так что найти что-либо можно будет буквально на несколько минут.
                Кстати, вы в курсе, почему нельзя начинать суффикс user-defined-literal'а с чего-либо кроме знака подчеркивания (для действительно пользовательских литералов, а не стандартных)? Ибо хотя грамматика в 2.14.18 говорит, что ud-suffix = identifier, если пройти по ссылке на 17.6.4.3.5, то там буржуйским по белому пишут: «начинать только с '_'», и в 2.14.18 добавляют — ill-formed. На это я наткнулся, когда написал код типа
                #define str «thing»
                cout << «some»str << endl; // имелась ввиду конкатенация литералов из 2.14.5(13)
                а компилятор дал мне отлуп. Говорит, это нифига не допустимый ud-literal.

                И да, вы правы, плюсовый стандарт — это один из самых замудренных документов, которые я только читал. Есть там и простые вещи, типа «If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.», так и сложные, типа «Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.».
                Но стоит отдать должное, фортрановский стандарт еще никто не смог перегнать по концентрации мути, например, чего только стоит воистину гениальное «Execution of a CONTINUE statement has no effect.» и с трудом понимаемое «Blanks may be inserted freely between tokens to improve readability; for example, blanks may occur between the tokens that form a complex literal constant.» Ну, про пробелы в фортране, я надеюсь, вы наслышаны.

                По поводу рода занятий. Все дело в том, что занимаюсь в основном разработкой компиляторов (конечно, пол-года-год назад я участвовал в разработке интерпретатора, но отличий в плане следования стандартам до последней запятой, уж поверьте мне, никаких). Не могу, к сожалению, сказать, что именно я разрабатываю, что принимаю на вход и что генерю на выходе. NDA, сами понимаете.

                Но, ИМХО, самая засада это не зубрежка уставов^W стандартов, а ситуация, когда два стандарта пересекаются. Например, OpenACC не описывает взаимодествие с фортрановским кодом не 2003-го стандарта. А мне нужно было добавить поддержку в компилятор, который поддерживает 2008-й стандарт. Разумеется, Самый Простой И Правильный Способ (СПИПС) это дать отлут на такой код. Но голова же дана не только чтобы форменный головной убор носить^W^W^W^W в нее есть, но и для того, чтобы найти Сложный Способ, Который Правильнее СПИПС. Я его, кстати, так и не нашел. До того, как я решил проблему с PARALLEL FOR, меня перекинули на другой проект. Теперь в GCC используется СПИПС.

                По поводу начинающего программиста. Если коротко, то профессионально (за деньги) я программирую уже пять лет. До этого еще десять готовил себя к этому. Однако, ИМХО, в компиляторстве я навсегда останусь новичком, ибо сколько бы уставов я не зазубрил, сколько бы оптимизаций не реализовал, сколько бы новых оптимизаций не изобрел, всегда найдется хитрый нюанс, который я не учел, и мой компилятор будет генерировать некоректный код. Если это произойдет завтра, то это выльется милиардными убытками (буквально) для Корпорации, в которой я сейчас работаю, через пол-года. Зато и удовольствие получаешь огромное от работы и удовлетворение. Заказчики — сплошь программисты, требования закреплены и не изменятся, разве что выйдет новая версия стандарта или новый процессор. Ну не вхожу я в ту элиту, которая кидается реализовывать все изменения после очередного драфта. Мне бы свои баги, которые живут годами, поправить.


                1. eyeofhell
                  07.07.2015 06:16

                  Какого, простите, хрена нарушать нулевое правило программирования (RTFM)?


                  в отличие от того же ES, который я полностью прочел и осознал где-то за пол-года, так как меня заставили


                  Я правильно понимаю, что вы предлагаете всем разработчиками js потратить от полугода на чтение стандарта? :). На HTML и CSS тоже по полгода закладывать?


                  1. ilmirus
                    07.07.2015 21:35
                    +4

                    Встречный вопрос: вы все man-pages прочли?

                    Нет, неправильно вы меня понимаете. Сам стандарт можно прочесть, не особо вдаваясь в суть, пролистывая разделы, с которыми не сталкивались, примерно за неделю по вечерам. Но его нужно прочесть! Это же все-таки не фортрановский стандарт, который требует довольно-таки неплохого знания языка. Затем к ним возвращаться в случае проблем или если уже достаточно поработали с фичами, которые в них описываются.

                    Например, можно пролистать раздел про automatic semicolon insertion и взять за правило всегда писать этот самый semicolon. По-моему, это правило довольно распространено. Если потом в один день обнаружили, что функция возвращает undefined всесто нужного значения из-за переноса выражения после return на новую строчку, то к нему следует вернуться и прочесть внимательно (этот раздел, ЕМНИП, занимает 3-4 страницы).

                    К слову, порядок обучения новому языку программирования у меня происходит примерно так — туториалы и книги по языку (начального и среднего уровня), пэт-проект на этом языке, быстрое чтение спецификации (где-то неделя поездок на работу и обратно и дома по вечерам), второй пэт-проект на этом языке. Оба проекта, естественно, относятся к парсингу. Пока что я пишу первый пэт-проект на Clojure. Rust я забросил на время, пока не появится спецификация.

                    Опять-таки, вас, случайно, не удивляет ситуация, когда сишники спокойно делают отсылки к стандарту, плюсовики обсуждают драфты (помню плач по концептам, хотя вроде никто магией шаблонов особо не увлекался) (может, это только мне так повезло, и я попал в хорошую компанию), а JS-эры пишут вместо этого статьи подобно этой?

                    Ах да, спасибо PVS-Studio за популяризацию стандартов, и осбенно за статью habrahabr.ru/company/pvs-studio/blog/250701 в коментариях к которой развернулось поистине эпичное обсуждение двух стандартов.

                    По поводу HTML и CSS ничего не могу сказать. Не знаком.


                1. jacob1237
                  08.07.2015 11:10

                  Вы в отделе маркетинга, случайно, не работали?
                  Такой ведь талант пропадает))


                  1. ilmirus
                    08.07.2015 15:12

                    Нет и не собираюсь.


          1. impwx Автор
            06.07.2015 11:49

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

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


        1. bay73
          05.07.2015 16:02
          +1

          Сообщество минусует ваш RTFM, потому что к данной статье он не очень уместен.
          Для большинства разработчиков чтение стандарта языка не даст ощутимой пользы (а может и наоборот быть вредно, так как бездумное использование всех возможностей, допускаемых стандартом, отнюдь не является признаком хорошего тона). А статья в этом смысле очень полезна — она демонстрирует малоиспользуемые возможности и поясняет, почему они малоиспользуемые.


      1. slonopotamus
        05.07.2015 11:01
        +2

        Вы так говорите, будто 574 страницы — это что-то гигантское.


        1. eyeofhell
          06.07.2015 09:44
          +1

          Художественного произведения — на два-три часа, если интересное. Стандарта языка программирования… Может и на пол года растянутся, если не зайдет.


        1. forgotten
          06.07.2015 10:34

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


          1. ilmirus
            07.07.2015 02:17

            /me механическим голосом скандирует «Убить всех человеков!» «Убить всех человеков!»


  1. Antelle
    04.07.2015 12:09

    > конструктор, принимающий длину, создает массив и задает свойство length, но не создает никаких элементов
    Это называется sparce array. Он ещё получится, если удалить что-то: delete arr[1].


  1. Anisotropic
    04.07.2015 12:25
    -2

    А что здесь должно происходить?

    var a, b, c;
    (a) = 1;
    [b] = [2];
    ({c} = { c: 3 });


    стабильный Хром не понимает пока левые части выражений и выкидывает ошибку


  1. iDennis
    04.07.2015 14:42
    -2

    Ещё можно было написать про свойства примитивов


  1. monolithed
    04.07.2015 15:15

    if (typeof Symbol !== "undefined") {
      // Symbol доступен
    }
    let Symbol = true; // вызывает синтаксическую ошибку в условии выше!
    


    Вы запутались. Пример для понимания:

    {
        typeof Symbol;  // ReferenceError
        let Symbol = true;
    }
    


    typeof Symbol;  // boolean
    
    {
        let Symbol = true;
    }
    


    1. impwx Автор
      04.07.2015 15:48
      +1

      Чем пример из статьи отличается от вашего первого?


      1. monolithed
        04.07.2015 16:00
        +1

        В вашем примере отсутствует блочная область, а это как-раз и есть основное отличие:

        {
            if (typeof Symbol !== "undefined") {
               // Symbol доступен
            }
        
            let Symbol = true;
        }
        


        1. impwx Автор
          04.07.2015 17:49

          Во-первых, это перевод и мотороллерпример не мой.

          Во-вторых, правильно ли я понимаю, что в глобальной области видимости let работает как-то иначе и ошибки не будет?


          1. monolithed
            04.07.2015 18:24

            Проще показать:

            {
              var i = true;
            }
            
            console.log(i); // true
            


            {
              let i = true;
            }
            
            console.log(i); // ReferenceError
            


            Иными словами, инструкция let, позволяет работать работать с блочной областью видимости, как если бы вы писали на С/С++ где такое поведение по-умолчанию.
            Вне блока, let ведет себя как инструкция var, за одним исключением циклов (долго спорили на этот счет).

            for (let i = 0; i < 10; i++) {
              console.log(i); // ....
            }
            
            console.log(i); ReferenceError
            


            Приведу классический пример:

            var array = [];
            
            for (let i = 0; i < 10; i++) {
              array.push(() => i);
            }
            
            console.log(array[1]()); // 1
            


            Тоже самое с var:

            var array = [];
            
            for (var i = 0; i < 10; i++) {
              array.push(() => i);
            }
            
            console.log(array[1]()); // 10
            


            Тоже правило распространяется и на такие выражения:

            var a = 0;
            
            let(a = 1) console.log(a); // 1
            
            console.log(a); // 0
            
            


            1. TheShock
              04.07.2015 22:13

              Нет. Это вы запутались



              let распространяется и на внутренние блоки.

              Вот тут ваш пример корректен, но он не имеет отношения к тому, который в статье:

              {
                let i = true;
              }
              
              console.log(i); // ReferenceError
              


              1. TheShock
                04.07.2015 22:15
                +1

                Я понял, просто в статье не совсем корректно сформулировано. Под комментарием "// Symbol доступен" имелось ввиду не то, что он реально доступен, а что если код дойдет туда — означает, что он доступен. Скажем, такие ожидания неопытного программиста. Естественно, ошибка выскочит еще на typeof


                1. monolithed
                  04.07.2015 22:49
                  -3

                  Ага, только вот неопытные разработчики редко обращают внимания на ошибки.


  1. Jabher
    06.07.2015 12:18

    что примечательно, делать return 1, 2, 3; в жс тоже можно.

    А вообще — половина пунктов от, пожалуй, непонимания.

    (function func(a, a) {  console.log(a)  })
        ("Привет", "Мир"); // выводит "Мир"
    

    Тут — разработчики изначальной спецификации не подумали о том, что разработчики будут так делать.
    Сейчас это используется примерно так:

    someWeirdAPI.registerCallbackWithArgs(function(_,_,_,_,usefulData){...})
    


    Подчеркивания позволяют повысить читаемость — проще увидеть количество аргументов, которые пропущены.
    Ломать обратную совместимость в случае с жс — очень плохая идея, поэтому ввели специальный режим strict.

    func(); // func объявлена, но не имеет значения, поэтому ошибка "func не является функцией"
    var func = function func() {
      console.log("Всё в порядке");
    };
    

    Все верно. В JS есть два типа декларирования переменной: variable (динамический тип) и function. Оба объявления hoist-ятся наверх в первом проходе компилятора (когда объявляются переменные). Function не может быть undefined, поэтому она сразу же регистрируется.

    Думаю, так будет понятнее:

    func1();
    func2();
    function func1(){};
    var func2 = function(){};
    

    превращается за счет hoist в
    function func1(){};
    var func2;
    
    func1();
    func2();
    
    func2 = function(){};
    


    До ES6 было широко известно, что с помощью оператора typeof можно безопасно узнать, объявлен ли идентификатор, даже если ему не присвоено значение:

    if (typeof Symbol !== "undefined") { // Symbol доступен }
    
    if (Symbol !== "undefined") { // Этот код выкинет исключение, если Symbol не объявлен}
    


    Бред. Так никогда не писали.
    Начнем с того, что нам нужно узнать, объявлена ли переменная, чтобы с ней можно было работать. В функциях нам это не надо, потому что мы сами их и объявляем, а если и надо — можно воспользоваться блоком try-catch, это крайне редкий кейс.
    Если мы работаем в глобальном пространстве — нужно пользоваться 'Symbol' in global<window, например> или .hasOwnProperty()
    Typeof дает возможность быстро провериться, но его использование почти всегда скорее мешает, его нужно использовать в двух случаях:
    1. определение типа для простых типов (для создания функций с несколькими интерфейсами)
    2. быстрая проверка типа объекта при создании API: a instanceof Object не работает, если a взят из айфрейма.

    С массивами все тоже очень забавно. Давайте-ка я попробую объяснить, как оно работает.

    z = new Array(10);
    z[10] = 5;
    Object.keys(z)
    ["10"]
    


    Статья забавная, в общем. Вроде бы и глубокая, а низлежащих механизмов жс-а не затронуто ваще ни разу.


  1. Asen
    06.07.2015 14:04
    -5

    Все высокоуровневые языки (которым является и js), казалось бы, должны избегать концепции «goto»(меток). Интересно, ключевую роль здесь играет обратная совместимость с XX веком?..


    1. mayorovp
      06.07.2015 14:20
      +4

      Во-первых, все-таки не надо путать goto и break. Второй оператор умеет «прыгать» только вперед и только из внутреннего блока во внешний — что сильно упрощает понимание кода с ним.

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