Pull to refresh

Оператор запятая

JavaScript *
Translation
Original author: Angus Croll
Продолжаем тему операторов, на этот раз вас ждет рассказ об операторе запятая.

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

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

'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 Принял во внимание и исправил недочеты в переводе
Tags:
Hubs:
Total votes 133: ↑120 and ↓13 +107
Views 33K
Comments Comments 49