I really didn't know what to call this question, neither what I could google for. I'm trying to understand the source code for the D3.js library and I've encountered two functions that I simply can't understand, due to the syntax that is new to me.
The first one is the number interpolator:
function d3_interpolateNumber(a, b) {
b -= a = +a;
return function(t) { return a + b * t; };
}
What's going on on the second line here? We're subtracting the value of b from the value of a and then...uhm, you lost me. How does this syntax work?
The other thing that confuses me, that I've seen in other places as well, is where the right-hand assignment of a variable consists of several variables separated by commas. As in:
var i = d3.interpolators.length, f;
What does this mean? These snippets are taken from https://github.com/mbostock/d3/blob/master/src/interpolate/number.js and
https://github.com/mbostock/d3/blob/master/src/interpolate/interpolate.js
The first line you're asking about is just two assignments. It's equivalent to this:
a = +a;
b -= a;
The +a is using the unary plus operator to convert a string to a number. So we are converting a to a number and then subtracting that number from b (and reassigning the new value to b).
The second bit of syntax you're asking about is simply a list of variable declarations. For example:
var a, b, c; // Declares 3 variables, all initialised to undefined
That's equivalent to this:
var a;
var b;
var c;
In your example, one of the declarations in the list also includes an assignment. Any number of them can, so this is valid too:
var a, b = 1, c = true, d;
An assignment is also an expression, which returns the value that is assigned. So this:
b -= a = +a;
is the same as:
b -= (a = +a);
or:
a = +a;
b -= a;
If the right hand side would really be values separated by comma, i.e:
var i = (d3.interpolators.length, f);
then the comma operator returns the value of the last operand, so it would be the same as:
d3.interpolators.length;
var i = f;
However, without the parentheses the comma is a separator between declared variables, not the comma operator, so it's the same as:
var i = d3.interpolators.length;
var f;
The second line is
b -= (a = +a);
Which means:
set a to +a (conversion to a number). Return this value outside of the parentheses.
Whatever value was returned, subtract it from b.
or
a=+a //converts a to an int
b-=a // or b=b-a
Remember, assignments return their value. So, alert(a=1) will alert 1.
On the other hand,
var i = d3.interpolators.length, f;
splits to:
var i = d3.interpolators.length;
var f;
This is just basically a way of saying "var applie to the following comma separated list"
Related
When playing around with JavaScript syntax it struck me that the following code will throw an error in SpiderMonkey and V8 engines:
var a = 1, b = 1;
a++++b;
This to me is strange, since the following works perfectly fine:
var a = 1, b = 1;
a+++b; // = 2; (Add a and b, then increase a)
// now a == 2 and b == 1
a+++-b; // = 1; (add a and -b, then increase a)
// now a == 3 and b == 1
In addition, the following would be nonsensical code:
var a = 1, b = 1;
a++ ++b; // throws an error
My argument is now that if a+++b is equivalent to a++ + b, and not to a+ ++b, and a+++-b is equivalent to a++ + -b, then a++++b can only be interpreted as a++ + +b in order for it to be valid JavaScript code.
Instead, the engines insist that a++++b is interpreted as a++ ++b, by operator precedence.
This to me is in contrast with the logic that the engines implements using the / symbol, as explained here, to distinguish between division and regular expressions. An example
var e = 30, f = 3, g = 2;
e/f/g; // == 5
e
/f/g; // == 5
/f/g; // is equivalent to new RegExp("f","g")
Here the argument is that because /f/g does not make sense as division in the last line, it is interpreted as a regular expression.
Obviously the / symbol gets a special treatment, in order to distinguish between division and regular expressions. But then why do ++ and -- not get a special treatment as well? (That is, outside operator precedence)
A second question is why operator precedence is not called only when the code is has multiple valid interpretations.
In the code a++++b you have two distinct statements: a++ and ++b with nothing to combine them. The + operator in the context of a++ + +b is actually a type converter (meant for turning strings into numbers) and has a different order of precedence which follows the others in the list.
I know the title is somewhat vague, but I'm not sure how to really explain this. So, in code
var a= 2, b=3;
a+=b;
//5
This is pretty basic javascript. Now I want to check if the result is larger than a certain number
var a= 2, b=3, c=4;
(a+=b) >= c;
//true
However, if I forget to add the parenthesis, I don't understand where the result could possible come from
var a= 2, b=3, c=4;
a += b >= c;
//2
I tried reading some stuff about order of operations and whatnot, but I still can't understand how that code can possibly output "2"
Because
a += b >= c;
is
a += (b >= c);
which is (in your case)
a += (false);
which ends up being
a += 0;
which is a.
The right-hand side of all of the assignment operators is evaluated before anything is done with the result. So b >= c is evaluated, giving us false, which is coerced to 0 when you try to treat it as a number with a +=.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript syntax: what comma means?
I came across the code while reading this article (do a Ctrl+F search for Andre Breton):
//function returning array of `umbrella` fibonacci numbers
function Colette(umbrella) {
var staircase = 0, galleons = 0, brigantines = 1, armada = [galleons, brigantines], bassoon;
Array.prototype.embrace = [].push;
while(2 + staircase++ < umbrella) {
bassoon = galleons + brigantines;
armada.embrace(brigantines = (galleons = brigantines, bassoon));
}
return armada;
}
What does the x = (y = x, z) construct mean? Or more specifically, what does the y = x, z mean? I'm calling it comma assignment because it looks like assignment and has a comma.
In Python, it meant tuple unpacking (or packing in this case). Is it the same case here?
This is the comma operator.
The comma operator evaluates both of its operands (from left to right)
and returns the value of the second operand.
The resultant value when a,b,c,...,n is evaluated will always be the
value of the rightmost expression, however all expressions in the
chain are still evaluated (from left to right).
So in your case, the assignations would still be evaluated, but the final value would be bassoon.
Result:
galleons = brigantines
brigantines = bassoon
armada.embrace(basson)
More information: Javascript "tuple" notation: what is its point?
var syntax allows multiple assignment, so when you see the following, you're declaring multiple variables using one var statement.
var a, b, c;
Note that this syntax is not the comma operator.
The , can be used as the comma operator. It simply evaluates a series of expressions. So when you see the following syntax, you are seeing a series of expressions being evaluated, and the return value of the last one being returned.
x = (y = x, z)
Within the parens, x is assigned to y, then z is evaluated and returned from the () and assigned to x.
I'd suggest that this syntax is unclear and offers little benefit.
The comma operand evaluates all of its operands and returns the last one. It makes no difference in this case if we had used
x = (y = x, z);
or
y = x;
x = z;
It's there to take away that line of code.
Is this valid JavaScript? I saw an example where someone used commas in the ternary operator conditions, and it was marked as an error in my editor, and the example didn't run in Chrome. However, it did run in Firefox. Once I converted all the ternary statements to if/else statements, the app ran on Chrome.
a!==b ? (a=1, b=2) : (a=2, b=1)
Edit:
This is the actual statement in the code:
a!==0?b<0?(h=b/a,e=h-1,f=-2*b+2*a*e,i=-2*b+2*a*h,d=2*h*a-2*b-2*a):(h=b/a,e=h+1,f=2*b-2*a*e,i=2*b-2*a*h,d=-2*h*a+2*b):d=h=e=f=i=0
Yes, it's valid, and it runs fine in Chrome:
var a, b, c;
a = 6;
b = 7;
c = a !== b ? (a = 1, b = 2) : (a = 2, b = 1);
console.log("a = " + a);
console.log("b = " + b);
console.log("c = " + c);
I'm not saying it's a remotely good idea in code humans are meant to read. :-) I expect jamietre is correct in the comments when he/she says it looks like the result of minification.
The comma operator is a binary operator (an operator accepting two operands). It evaluates its left-hand operand (thus causing any side-effects it has, such as assignment), throws that result away, then evalutes its right-hand operand (thus causing its side-effects if any) and takes that result as its result value. If you have multiple comma operators in a row, the overall expression is evaluated in order, left-to-right, with the final result being the value resulting from the right-most operand evaluation.
And of course, you know the conditional operator (a ternary operator — one accepting three operands) is used to pick one of two sub-expressions to evaluate, on the basis of an initial expression.
So that line is very...expressive...what with a total of seven* different expressions inside it.
So in that example, the result of the overall expression is 2 if a !== b initially, or 1 if a === b initially, with the side-effects of setting a and b.
It's the side effects that make it, in my view, a questionable choice. And of course, there's no reason to use the comma operator if the left-hand operand doesn't have side effects.
* Yes, seven of 'em packed into that overall ternary:
a !== b
the first comma expression
a = 1
b = 2
the second comma expression
a = 2
b = 1
Re your edit with the actual statement, that one works too:
function test(a) {
var b = 7,
d = 1,
e = 2,
f = 3,
g = 4,
h = 5,
i = 6;
a!==0?b<0?(h=b/a,e=h-1,f=-2*b+2*a*e,i=-2*b+2*a*h,d=2*h*a-2*b-2*a):(h=b/a,e=h+1,f=2*b-2*a*e,i=2*b-2*a*h,d=-2*h*a+2*b):d=h=e=f=i=0;
console.log("a = " + a);
console.log("b = " + b);
console.log("d = " + d);
console.log("e = " + e);
console.log("f = " + f);
console.log("g = " + g);
console.log("h = " + h);
console.log("i = " + i);
}
test(0);
test(1);
.as-console-wrapper {
max-height: 100% !important;
}
But wow, I hope this is minified, because if a person wrote that, they must really have a thing against anyone who's supposed to maintain it later... ;-)
Yes:
a=1;
b=2;
a!==b ? (a=1, b=2) : (a=2, b=1)
console.log(a); // 1
console.log(b); // 2
and:
a=1;
b=2;
a===b ? (a=1, b=2) : (a=2, b=1)
console.log(a); // 2
console.log(b); // 1
As you can analyze, changing the equality operator reacts correctly to our test if you look at the results.
Or you can do this :
b = a!==b ? (a=1,2) : (a=2,1);
Read here about comma operator.
The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.
Expanding on this topic with ES6 code example. If you're using one side of the TRUE : FALSE argument to iterate thru all cases in one IF, it makes sense to separate the code as if it's a switch | case statement.
Nesting implies that there is branching logic, while it is logically nested, writing nested IF's complicates what we're doing in my example. Like a lawyer over explaining a problem to a jury. IMO, you want to explain the point in it's simplest form. For instance, I find this example the most logical way of expressing nested ifs where the TRUE is executed. The final false is your last else {}
choreDoor is either 0,1 or 2:
choreDoor === 0 ?
(openDoor1 = botDoorPath,
openDoor2 = beachDoorPath,
openDoor3 = spaceDoorPath)
: choreDoor === 1 ?
(openDoor2 = botDoorPath,
openDoor1 = beachDoorPath,
openDoor3 = spaceDoorPath)
: choreDoor === 2 ?
(openDoor3 = botDoorPath,
openDoor1 = beachDoorPath,
openDoor2 = spaceDoorPath)
: false;
If you don't want to use the Comma operator (,) then you can use nested Conditional (ternary) operators instead.
var a = 6;
var b = 7;
var c = (a !== b)? // true
((a = 1 || 1===1)? (b = 2) : null) // will first run a=1, then b=2
: ((a = 0 || 1===1)? (b = 0) : null);
console.log("a = " + a);
console.log("b = " + b);
console.log("c = " + c);
So I'm trying to take the variable that increments in a for statement, and add an integer to it... but for some reason, it's adding the integer as though it were a string; other operations like subtraction or multiplication work as expected.
Why is this happening? Edit: I've added the whole function; the problem in question is where I try to add 2 to the variable x.
What confuses me is that I'm able to use x no problem, in an .eq() object for example...
$(function() {
$('textarea').bind('paste', function (e){
inputGroup = $(this).parent();
var ob = $(this);
if (e.type == 'paste'){
setTimeout(function(){
var data = ob.val();
var tabbed = data.replace(/\n/g, "\t");
var cells = tabbed.split("\t");
for(var x in cells) {
foo = x + 2;
alert(foo);
$(inputGroup).find('input').eq(x).val(cells[x]);
}
}, 1);
}
});
});
Why is this happening?
Because x is a string that just looks like a number. Cast to Number first and you'll get the result you expect:
"1" + 2 = "12"
Number("1") + 2 = 3
EDIT : Now that I see you are using split to turn a string into an array, your problem is definitely that you are concatenating strings. Cast to Number first, and your problem is solved.
Yes other arithmetic operations will work, since they will implicitly cast the operands to Numbers. Confusingly, "2" * 3 will in fact evaluate to the integer 6. Welcome to Javascript.
-tjw
Without more code, specifically the initialization of cells, I can't tell you the exact reason. But you can simply call parseInt() on x to turn it into an integer for addition
for(var x in cells) {
foo = parseInt(x, 10) + 2;
$(inputGroup).find('input').eq(foo).val(cells[x]);
}
Because + is a String concatenation but there is no equivalent String method for * or / so when using those it cast the value as a Number. Just cast x as an integer:
for(var x in cells) {
foo = parseInt(x, 10) + 2;
$(inputGroup).find('input').eq(foo).val(cells[x]);
}
The 10 in parseInt is saying to use a base 10 number system (as opposed to hex 16, e.g.).
As others have mentioned, x is a string. That's why.
There's a nice trick for casting strings as numbers in JavaScript that hasn't been mentioned though:
for(var x in cells) {
// Without the "var" here, you're setting a global
// variable during each loop iteration.
var foo = +x + 2;
$(inputGroup).find('input').eq(foo).val(cells[x]);
}