逗号操作符(笔记)

What does it do?

The comma operator evaluates both of its operands (from left to right) and returns the value of the second operand. (MDC)

 

var a = (7, 5);
a; //5

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


 
Why did you wrap those variable assignments in parentheses?

 

Because of operator precedence. A JavaScript statement can contain multiple, disparate operators. The following statement has three operators (*+ and ,) :

 

return 5 * 2 + 3,  22;


 
Operator precedence determines the order in which operators are evaluated within a statement. The full list, in order of precedence is here. The comma operator has the lowest precedence of any operator. Lets simulate how this applies to the above example:

 

 

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


 
Now let’s use that knowledge to see what would happen if we hadn’t wrapped the variable assignment in parentheses:

 

 

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


 
By wrapping the right hand expression in parentheses we create a group – which, effectively has the highest precedence. This ensures that the comma operator gets applied first:

 

 

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


 
In practice, lowest operator precedence actually makes the comma operator quite powerful. In effect it says: go ahead and see to all those other little operations first, then watch me come and clobber the result.

 

Some statements contain multiple commas. How does that work?

The above rule still applies. Each comma operator in the statement is processed in sequence from left to right.

 

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


 
This is equivalent to:

 

 

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


 
What about commas used in type literals and declarations?

 

These are comma separators not comma operators. The purpose of a comma separator is to delimit members in a list. For example:

 

//set 4 array elements
var arr = [1, 2, 3, 4];

//create an object with 2 properties
var obj = {
  a: 22,
  f: function() {return this.a*this.a}
}

//define 3 distinct variables
var a = 1, b = 2, c = 3;

//invoke a function passing 2 arguments
Math.max(4, 7);


 
Why use comma operators?

 

Because they let you specify more than one expression where JavaScript expects only one. Comma operators are rarely essential but often useful and just occasionally downright elegant:

 

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


 
Isn’t the comma operator just a semicolon in disguise?

 

Semicolons partition statements. Comma operators partition expressions within statements.

Why wouldn’t I just use the && operator to evaluate multiple expressions sequentially?

The comma operator is a close cousin of the && and || operators. All three operators will return the last expression they evaluate. The distinction is straightforward:

 

//(LHE: left hand expression, RHE right hand expression)

LHE && RHE
1. Always evaluate LHE
2. If LHE is true, evaluate RHE

LHE || RHE
1. Always evaluate LHE
2. If LHE is false, evaluate RHE

LHE, RHE
1. Always evaluate LHE
2. Always evaluate RHE


 
Choose the comma operator when both expressions must always be evaluated.

 

How about some more examples?

Okay. Earlier on I mentioned that comma operators let you specify more than one expression where JavaScript expects only one. This is perhaps most useful within the confines of the for loop:

for loops

Here’s an alternate version of a fibonacci generator, also using the comma operator:

 

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" 


 
For another example, consider a utility that helps a store clerk select the bills and coins that make up a customer’s change. Here’s the basic version. We use a comma operator to bisect the second statement of the for loop. This lets us neatly increment our currency counter before testing against the limiting expression:

 

 

 
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]


 
Now here’s the same utility with added formatting for user-friendliness:

 

 

 
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"


 
This following function uses the comma operator to simultaneously increment and decrement two counters within a for loop. The product of the counters is used to render a rather fetching curve in the console:

 

 

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

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


 
while loops

 

You can use a comma operator to create a succinct version of the do-while loop. This routine searches an elements ancestry looking for a tag name match. Again we use the comma to perform an action prior to checking the limiting expression:

 

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">


 
Ternary conditionals

 

Ternary syntax allows for only one statement in each of its three components. As a general rule, if you need to use more statements you should consider using if elseinstead. However it’s sometimes more readable when the comma operator is used to combine short succinct expressions within a ternary statement:

 

//player loses
lives ? (lives--, go()) : (gameOver(), exit());


 
Debugging

 

The comma operator provides an unobtrusive way to inject console logs into your code without having to reformat (can you spot the errors that necessitated debugging in each case?)…

 

//CONTAINS AN INTENTIONAL ERROR!!!
//sum products while i > n
var i=10, n=0, total=0;
while(console.log(i,n), i-- > n++); {
    total += i*n
}

 

 

//CONTAINS AN INTENTIONAL ERROR!!!
//sum an array
var arr = [1,2,3];
for (
    var i=0, total=0;
    i<arr.length;
    console.log(i,total), total += arr[i++]);
)

 

 

//CONTAINS AN INTENTIONAL ERROR!!!
//add 4 to members of array and sum it
//(yes there are easier ways to do this!)
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)});


 
Binding with iterators

 

@wavded posted this nifty technique for unobtrusively resetting iterators. Again, you don’t need to do it this way – but the tidiness appeals to me:

 

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

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


 
Indirect calls to eval

 

eval¹ calls are normally invoked within their containing context (i.e. the this value in the evaluated code will be the same as the the this value of the surrounding code). This is problematic since there is no guarantee that repeated eval calls will originate in the same context.

As @kangax describes here, we can use the comma operator to fashion an indirect call to eval which will force it to execute in the global context²:

 

var a = {};

//attempt eval in context of object <code>a</code>
(function() {
    eval("this.alert('If you can read this I must be global!')");
}).call(a);
//TypeError: this.alert is not a function

//force eval in global context
(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!'


 
¹ discussion of the merits of eval are beyond the scope of this article ;-)
² although the ES5 standard confirms that indirect calls to eval should run in the global context, not every browser is compliant (i.e. IE <= 8).

 

Wrap Up

You could probably write perfectly good JavaScript code without ever using the comma operator. Does this mean I just wasted your time? I hope not. Just as an extensive vocabulary makes us better speakers and writers, so a comprehensive access to language features should make us better coders. The more techniques we have at our disposal the greater our ability to write elegant, succinct and readable code. Have fun with comma operators and please share your neat usage examples!

posted @ 2012-04-20 14:40  dushaobin  Views(392)  Comments(0Edit  收藏  举报