Пользователь
0,0
рейтинг
4 апреля 2011 в 19:29

Разработка → Оператор запятая перевод

Продолжаем тему операторов, на этот раз вас ждет рассказ об операторе запятая.

Начнем с забавного твита:

Если изображение не отображается, пожалуйста, напишите автору!

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

Что она делает?


Оператор запятая выполняет оба операнда (слева направо) и возвращает значение второго оператора. (MDC)
var a = (7, 5);
a; //5

var x, y, z
x = (y=1, z=4);
x; //4
y; //1
z; //4

Почему в вашем примере присваивания переменных окружены круглыми скобками?


Из-за приоритета операторов. Выражение JavaScript может содержать несколько различных операторов. Следующее выражение содержит три оператора (* + и ,):

return 5 * 2 + 3,  22;

Приоритет операторов определяет, в каком порядке будут выполнены операнды внутри выражения.
Полный список операторов тут. Оператор запятая имеет наименьший приоритет из всех операторов. Давайте посмотрим на примере:

//original
return 5 * 2 + 3,  22;
//apply * operator
return 10 + 3,  22;
//apply + operator
return 13, 22;
//apply , operator
return 22;

Сейчас давайте посмотрим что будет если мы уберем круглые скобки:

//original
var a = 7, 5;
//apply = operator
var a, 5; //a is now 7
//SyntaxError: missing variable name

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

//original
var a = (7, 5);
//apply group
var a = 5;

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

Некоторые выражения содержат несколько запятых. Как это работает?


Каждый оператор в цепочке обрабатывается последовательно слева направо.

var a = (1, 2, 3, 4);
a; //4

Это эквивалентно:

var a = (((1, 2), 3), 4);
a; //4

Что насчет запятых, использующихся в литералах типа и в объявлениях?


Эти разделители на самом деле не операторы запятая. Назначение разделителя-запятая — разделение членов в списке. К примеру:

// создает массив из 4 элементов
var arr = [1, 2, 3, 4];

//создает объект с двумя свойствами
var obj = {
  a: 22,
  f: function() {return this.a*this.a}
}

//определяет три переменные
var a = 1, b = 2, c = 3;

//вызывает функцию, передавая 2 параметра
Math.max(4, 7);

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


Затем, что она позволяет вам выполнить несколько выражений в том месте, где JavaScript ожидает только одно. Выражения с оператором запятая не такие распространенные, редко важные, но очень элегантные:

var r = [], n = 0, a = 0, b = 1, next;

function nextFibonacci() {
    next = a + b;
    return b = (a = b, next); // <<< Вот тут
}

while(n++ < 10) {
    r.push(nextFibonacci());
}

r; //[1, 2, 3, 5, 8, 13, 21, 34, 55, 89]


function getRandomPrime() {
    while(n = Math.round(Math.random()*1000000000), !isPrime(n)); // <<< Вот тут
    return n;
}

var isPrime = function(n) {
    d = Math.ceil(Math.sqrt(n));
    while(n%(d--) && d);
    return !d;
}

getRandomPrime(); //425593109
getRandomPrime(); //268274719

Разве точка с запятой — не замаскированная запятая?


Точка с запятой — это разделитель объявлений, а запятая — это разделитель выражений внутри объявлений.

Почему бы не использовать оператор && для выполнения нескольких выражений последовательно?


Оператор запятая очень близка к оператору && и ||. Все эти операторы возвращают последнее выражение, которое они выполнили. Вот в чем их различие:

LHE — левое выражение
RHE — правое выражение

LHE && RHE
1. Всегда выполняет LHE
2. если LHE — true, выполняет RHE

LHE || RHE
1. Всегда выполняет LHE
2. Если LHE — false, выполняет RHE

LHE, RHE
1. Всегда выполняет LHE
2. Всегда выполняет RHE

Вам следует выбрать запятую, если оба выражения должны быть выполнены.

Как насчет примеров?


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

Циклы for

Вот альтернативная версия генератора чисел Фибоначчи, которая также использует оператор запятая:

for (
    var i=2, r=[0,1];
    i<15;
    r.push(r[i-1] + r[i-2]), i++
); 

r //"0,1,1,2,3,5,8,13,21,34,55,89,144,233,377"

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

function toCurrency(total, values) {
    total *= 100;
    for(
        var i=0,counts=[];
        counts[i]=total/values[i], total=total%values[i]; // Вот тут
        i++
     );
     return counts.map(Math.floor);
} 

toCurrency(32.47, [500, 100, 25, 10, 5, 1]); //[6, 2, 1, 2, 0, 2]

Та же самая утилита, но с форматированием:

function toCurrency(total, values, sym) {
    total *= 100;
    //do the calc
    for(
        var i=0,counts=[];
        counts[i]=total/values[i], total=total%values[i]; // Вот тут
        i++
    );
   //format
   var results = counts.map(function(s,i) {
       return s>=1 && [Math.floor(s),"x",(sym || '$') +
            (values[i]/100).toFixed(2)].join(' ');
    });
    return results.filter(Boolean).join(', ');
}

toCurrency(19.77, [500,100,25,10,5,1]);
//"3 x $5.00, 4 x $1.00, 3 x $0.25, 2 x $0.01"
toCurrency(19.77, [500,100,50,20,10,5,1], '£');
//"3 x £5.00, 4 x £1.00, 1 x £0.50, 1 x £0.20, 1 x £0.05, 2 x £0.01"
toCurrency(19.77, [500,100,50,20,10,5,2,1], '€');
//"3 x €5.00, 4 x €1.00, 1 x €0.50, 1 x €0.20, 1 x €0.05, 1 x €0.02"

Следующая функция использует запятую для одновременного увеличения и уменьшения двух переменных внутри цикла. На выходе получаем кривую:

function renderCurve() {
  for(var a = 1, b = 10; a*b; a++, b--) // Вот тут
    console.log(new Array(a*b).join('*'));
}

renderCurve();
/*
*********
*****************
***********************
***************************
*****************************
*****************************
***************************
***********************
*****************
*********
*/

Циклы while

Вы можете использовать оператор запятая для создания кратких версий циклов do-while.
Эта функция ищет предка из списка элементов с именем tagName (аналог jQuery parent).

function firstAncestor(el, tagName) {
  while(el = el.parentNode, el && (el.tagName != tagName.toUpperCase()));
  return el;
}

//element in http://ecma262-5.com/ELS5_HTML.htm
var a = $('Section_15.1.1.2'); 

firstAncestor(a, 'div'); //<div class="page">

Тернарный оператор

Тернарный оператор позволяет выполнять только одно выражение. Если вам необходимо выполнить несколько выражений, то вам приходится переходить на if else. Оператор запятая более читаемый в тех случаях когда он используется для комбинации коротких выражений:
//player loses
lives ? (gameOver(), exit()) : (lives--, go());

Дебаг

Оператор запятая позволяет вам вставлять console.log в любое место без изменения кода:

//складывает products пока i > n
var i=10, n=0, total=0;
while(console.log(i,n), i-- > n++); {
    total += i*n
}


//складывает элементы массива
var arr = [1,2,3];
for (
    var i=0, total=0;
    i<arr.length;
    console.log(i,total), total += arr[i++]);
)


//добавляет 4 к каждому элементу массива и складывает их
var testArray = [3, 5, 8, 4], total = 0;
var plusFour = testArray.map(function(e) {e + 4})
plusFour.forEach(function(n) {console.log(n), isNaN(n) || (total += n)});

Связывание с итераторами

@wavded опубликовал один способ применения запятой.

var colorIndex = 0,
    colors = ["FF0000", "008000", "FF0086", "A2FF00", "0000FF", "800080"]; 

function selectNextColor(){
    return colors[colorIndex++] || colors[colorIndex = 0, colorIndex++];
}

Косвенный вызов eval

eval1 использует тот контекст в котором был вызван. Поэтому нет гарантий, что вызов eval в цикле даст тот же самый результат.

kangax написал, что мы можем использовать оператор запятая для косвенного вызова eval, который будет всегда вызываться в глобальном контексте2:

var a = {};

(function() {
    eval("this.alert('If you can read this I must be global!')");
}).call(a);
//TypeError: this.alert is not a function

(function() {
    (0,eval)("this.alert('If you can read this I must be global!')");
}).call(a);
//alerts: 'If you can read this I must be global!'

1. Пожалуйста без холиваров, все знают, что eval — evil
2. В стандарте ES5 написано, что любой не прямой вызов eval использует глобальный контекст,
однако не все браузеры поддерживают это правило (IE<=8)

Заключение


Вы можете писать отличный код и без использования оператора запятая. Значит ли это, что я потратил ваше время? Я надеюсь, что нет. Обширный словарный запас делает писателей и ораторов более профессиональными так и доступ к широком возможностям языка может сделать из нас лучших кодеров. Чем больше методов мы знаем тем более красивый, аккуратный и читаемый код мы можем написать. Удачи с оператором запятая, поделитесь своими примерами использования!

Почитать


ECMA-262 5th Edition
11.14 The comma operator
10.4.2 Entering eval code
15.1.2.1.1 Direct Call to Eval

Mozilla Developer Center
comma operator
operator precedence

Juriy Zaytsev ( kangax): global eval, what are the options
Mark Harter ( @wavded): cycling through an array using the comma operator

UPD Принял во внимание и исправил недочеты в переводе
Перевод: Angus Croll
Mikhail Davydov @azproduction
карма
448,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

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

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

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

  • –1
    Просветили, спасибо!
  • –3
    Спасибо большое!
  • –5
    Когда читал английскую версию это звучало как Comma оператор и я из за моего Bad English подумал то что есть какой то оператор из за которого движок js виснет но когда прочитал статью все встало на свои места
    • –2
      comma != coma // ;)
  • 0
    Только сегодня наконец-то прочитал эту статью в оригинале, а тут уже перевод.
  • +13
    Оператор очень запятая близка к оператора

    «Очень запятая» это более крутой оператор чем обычная запятая :D

    З.ы.: И, вообще, начало предложения странное.
    • +6
      Оператор очень запятая близка к оператора и его жена были в основном из за того что я не могу понять как это сделать в домашних условиях и в порядке надзора судебных актов отказано в связи с чем в настоящее время в России в начале ХХ века в России было продано около двух лет назад в России был принят закон о запрете курения в общественных местах и на улицах города и в его окрестностях в том числе и в России в начале ХХ века
      (с) scribe.googlelabs.com
  • +1
    Спасибо! Не знал таких подробностей. Еще одна техника нинздя-кодера :)
    • +21
      «Чем больше неочевидных конструкций языка я задействую — тем незаменимей я стану»
    • +14
      Ниндзя — это хорошо. Навыки ниндзи очень пригодятся такому кодеру при встрече с человеком, которому доведется поддерживать такой код.
      • 0
        Если не применять навыки ниндзя при написании комментариев, то скорее всего не пригодятся.
    • НЛО прилетело и опубликовало эту надпись здесь
      • +1
        А мне казалось, что любой, кто знает Си (с которым культурно связан жаваскрипт) знает этот оператор.
  • 0
    Оператор запятая выполняет оба оператора (слева на право) и возвращает значение второго оператора.
    Не оператора, а операнда и лучше сказать «вычисляет».
    С этим, кстати, связан такой довольно забавный пример:
    alert( typeof(someUndefinedValue) )
    alert( typeof(window, someUndefinedValue) )


    Как можно видеть, оператор группировки (в простонародье скобки) не вычисляют значения (используемая мною тут терминология несколько отличается от той, что применяется в стандарте) операнда. Поэтому (eval)('la-la-la') и eval('la-la-la') эквиваленты. Зато (0, eval) заставит содержимое скобок вычислиться и в результате мы получим eval, «открепленный» от своего контекста по-умолчанию.
  • +5
    Назначение разделителя запатая — разделение членов в списке.
  • –1
    statement = оператор (присваивания, условный, цикла и т.д).
    operator = операция (сложения, возведения в степень, и т.д).
    • 0
      Operator Comma ближе к Оператор Запятая по аналогии с Оператор побитового сдвига влево
      statement
      1) утверждение; высказывание; формулировка
      2) оператор; предложение
      operator
      1) оператор(см. тж statement); знак операции
  • +23
    Знание оператора запятая это, конечно, хорошо. Но если вместо

    return colors[colorIndex++] || colors[colorIndex = 0, colorIndex++];

    вы напишете

    return colors[(colorIndex++) % colors.length];

    то тот, кто будет поддерживать этот код, будет реже вспоминать вас матом.
  • НЛО прилетело и опубликовало эту надпись здесь
  • +13
    Любой дурак может написать код, понятный компьютеру. Хороший программист пишет код, понятный человеку.
    Мартин Фаулер
    — Ну и «Пишите код, ожидая что его будет поддерживать с замашками маньяка и садиста»
    • +1
      «знающего ваш домашний адрес». Это важно.
  • –2
    У Пола Айриша в блоге как-то встретил:
    // Detect IE in JS using conditional comments
    var ie = (function(){
     
        var undef,
            v = 3,
            div = document.createElement('div'),
            all = div.getElementsByTagName('i');
     
        while (
            div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',  // <<< Вот тут
            all[0]
        );
     
        return v > 4 ? v : undef;
     
    }());
    

    Автор
    • +3
      Ужас. Для определения IE придумано множество других приемов, которые гораздо короче в написании и работают, не затрагивая DOM модель.
      • –1
        Хм… А сколько из них не используют UA?
        • +1
          Много. Впрочем, я не заметил то, что этот скрипт еще и версию определяет, а альтернативных, не использующих DOM, я не знаю.
        • –1
          Все User Agent известны почему бы их не использовать.
          В JavaScript где, в отличии от css/html, возможности велики наоборот отходят от хаков: см. jQuery, UglifyJS 'v'=='\v' всегда переделывает в !1 (Won't Fix bug)…
          Просто рано или поздно эти баги/квирксы могут пофиксить (прим. IE9 'v'=='\v'), а вот форма UA не поменяется.
  • +1
    В С++ его еще и перегружать можно, что ведет к очень интересным эффектам, к сожалению, в большинстве своем это рождает говнокод.
  • 0
    Опа, интересная заметка. Спасибо.

    Только как интерперататор будет отличать оператор от разделителя?

    Например, в следующем вызове я создаю массив с 2-мя элементами 5 и 7 или массив длиной 7?

    var a = new Array(5,7);
    • 0
      Вот из-за таких вопросов лучше создавать масивы так:
      var a = [5, 7]; //два элемента
      var a = Array(n); //n — элементов
      и никогда не смешивать :)
      • +1
        А тут в чем разница?
        То ли [5, 7] это [(5, 7)]
        То ли [5, 7] это [5, 7]

        А вот тут нам на помощь приходит приоритет операторов. У запятой самый низкий приоритет из всех операторов, а у квадратных скобок и вызовов функций (скобки в Array() и просто скобки () это не одно и то же) гораздо выше.
        • 0
          да, что скобки и создание нового объекта не одно и то же — понятное дело. Но ваш ответ прояснил ситуацию — дело именно в приоритете, спасиб )
          • 0
            Извините, мое предыдущее сообщение не соответствует действительности.

            Тут нет оператора запятой. Я ошибался и дело не в приоритете, а в том, что Array(...) — вызов функции, а [...] — инициализатор массива. Они оба принимают список аргументов с запятой в качестве разделителя. А вот [] как оператор индексирования хоть и имеет больший приоритет, но на операторе-запятой это никак не сказывается.

            alert([3,4,5,6][1,2])
            В этой записи [3,4,5,6] — инициализатор массива, а [1,2] — оператор индексирования (доступа к свойствам), у которого «аргумент» (операнд) — простое выражение, в вычислении которого участвует оператор-запятая (в первом случае ей было неоткуда взяться, т.к. по ней разбивается список аргументов; исключение составляет выражение в скобках).
            • 0
              да, я тоже ошибся, приняв приоритет за правильный ответ. Контекст — вот более правильно.

              Но вопрос тогда остался открытым (я его задал ниже) — как интерпретатор JS отличает в подобных местах, что конструкцию следует рассматривать именно как перечисление двух аргументов, а не как команду обсчитать выражение, в котором участвует оператор запятая. Ведь выражение является валидным аргументом, а оператор запятая — валидна в выражении.
              • 0
                Как я понял из беглого просмотра стандарта,
                Список аргументов разбивается по запятым на особые выражения (ВыражениеПрисваивания в документе по ссылке выше), которые не могут содержать оператор-запятую (Зато могут содержать оператор группировки (скобки), которые уже могут содержать выражения (Выражение в терминах стандарта) с оператором-запятой). Поэтому никаких проблем с неопределенным смыслом не возникает. А вот для оператора-группировки «аргумент» (операнд) является Выражением, т.е. позволяет использовать запятую.
    • 0
      Тут дело не в запятой, а в поведении конструктора Array.
      В вашем случае создается массив [5, 7] потому, что конструктор Array имеет следующую логику:
      1. Если arguments.length === 1 && typeof arguments[0] === 'number' && (arguments[0] < 0 || isNaN(arguments[0]) && !isFinite(arguments[0])), то выбрасывается исключение RangeError
      2. Иначе если arguments.length === 1 && typeof arguments[0] === 'number', то создается массив длиной arguments[0]
      3. Иначе создается массив из arguments:
      new Array(Infinity); // RangeError
      new Array(-1); // RangeError
      new Array(NaN); // RangeError
      new Array(NaN, NaN); // [NaN, NaN]
      new Array(undefined); // [undefined]
      new Array(1); // [undefined]
      new Array(0); // []
      new Array(0, 0); // [0, 0]
      
      • –1
        хехе, вы попались на ту же удочку, что и pietrovich выше :) дело в том, что тут уже не важно, какая логика внутри конструктора, так как тут вопрос о том — что за аргументы передаст этой логике конструкция new Array(5, 7).

        Чуть выше ситуация прояснена — дело именно в приоритете, так как запятая самая низкоприоритетная, то тут сработает оператор вызова конструктора и передаст содержимое скобок как 2 аргумента.
        • 0
          хехе, вы попались на ту же удочку
          Чуть выше ситуация прояснена — дело именно в приоритете, так как запятая самая низкоприоритетная, то тут сработает оператор вызова конструктора и передаст содержимое скобок как 2 аргумента
          Если бы тут был именно Оператор Запятая, а не Разделитель Запятая, то по своей логике (выполняет все операнды и возвращает последний) в массив бы она передала только 7. Помоему, это Вы плохо читали.

          Вы утверждаете, что и тут запятая — Оператор Запятая: Math.min(1, 4, 5)?
          • 0
            Вы правы, здесь действительно нет оператора-запятой.
            Моя вина, ввел хабраюзера Zerkella в заблуждение.
            • 0
              Не переживайте, все ок. Идею я понял — дело в контексте.
        • –1
          Передаст 5 и 7 потому, что запятая это Разделитель Запятая, а не Оператор Запятая, а скобки это Function Invoke выражение, а не оператор группировки.
          Я исчерпывающе ответил на ваш вопрос выше:
          Например, в следующем вызове я создаю массив с 2-мя элементами 5 и 7 или массив длиной 7?
          var a = new Array(5,7);
          • 0
            Не сердитесь вы так сильно :)

            Лучше подскажите, по каким правилам технически при разборе текста интерпретатор отличает оператор запятую от разделителя запятой?

            • 0
              Пример:
              alert
              ( ( 2 * 2, 0 ), 2);
              Интерпретатор удаляет все ненужные невидимые символы, ставит где это необходимо запятые, проводит валидацию.
              alert((2*2,0),2)
              1. Интерпретатор «видит» alert — эта конструкция подходит по описание переменной
              2. Видит, что после переменной стоят скобки — первые скобки Function Invoke выражение.
              3. Из-за того, что скобки Function Invoke выражение, то воспринимает запятую перед двойкой как Разделитель Запятая (перечисляет список переменных функции).
              4. Начинает по очереди резолвить переменные.
              4.1. Доходит до первого аргумента (2*2,0) — это выражение. Исходя из приоритета операций (сперва () затем * затем ,) резолвит это выражение — получаем первый аргумент = 0.
              4.2. Доходит до второго аргумента — константа (резолвить не надо) = 2.
              5. Далее резолвит объект на который указатель alert смотрит (находит только в global scope) если объекта нет, то выдает ошибку ReferenceError.
              6. Смотрит, что объект на который ссылается alert есть Callable — вызывает Callable Object aka Function на который ссылается alert с параметрами 0, 2. Если он не Callable, то выбрасывает ошибку TypeError.
              7. Дальнейшее поведение alert всем известно.

              В зависимости от интерпретатора он может не убрать точку с запятой, тогда в довесок вызовется ещё и «пустой оператор».
  • 0
    Что-то не очень оно элегантно [читабельно] — записывать несколько выражений в одну строчку, вместо многих, что показано в большинстве Ваших примеров.
  • +1
    Еще один пример использования оператора запятая — вызов Function Expression.

    Function Declaration (будет ошибка):
    function(){}()

    Function Expression (как в jQuery):
    (function(){})()

    Function Expression (с оператором запятая):
    1, function(){}()

    P.S. На практике я бы использовать не стал :)
    • 0
      я вот даже не знаю, что лучше — Function Expression (как в jQuery) или Function Expression (с оператором запятая). Мне не очень нравится количество скобок в конце такого выражения:
      (function () {
          // code
      })();
      

      Пока использую через new, но смущает отсутствие глобального контекста в this.
      new function () {
          // code
      };
      
      • 0
        Facebook Style
        !function (window, document) {
            // code
        }(this, document);
        
  • 0
    Кстати, для выполнения кода в глобальном контексте можно создавать функции через конструктор Function:

    var a = {};
    (function() {
        new Function("this.alert('If you can read this I must be global!')")();
    }).call(a);
  • 0
    Вот как полезно писать «забавные» твиты, оказывается. :)

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