Using the ternary operator with string concatenation
alert("test: "+(1==2)?'hello':'world');
This should show me 'world'
on the screen since 1 is not equal to 2.
How come it alerts 'hello'
?
Solution 1:
Try wrapping your parens around the operation
alert("test: "+ (1 == 2 ? 'hello' : 'world'));
demo: http://jsfiddle.net/hunter/K3PKx/
what this is doing:
alert("test: "+(1==2)?'hello':'world');
is evaluating "test: " + (1==2)
as true
which outputs 'hello'
Solution 2:
Both of the submitted answers are correct, you need to add parentheses. I figured I'd talk briefly about why.
alert("test: "+(1==2)?'hello':'world');
When the parser encounters a statement, it will begin recursively breaking it down into smaller and smaller chunks.
In this case, the first thing it encounters is a function: alert
. Immediately the parser will look at alert
's arguments, and begin parsing each of them individually. This function only has one argument, "test: "+(1==2)?'hello':'world'
, making it an easy first step.
At this level we can break our statement down into a series of binary comparisons. Javascript parsers form binary pairs from left-to-right (when order of operation values are the same). Our potential candidates are "test: "
, (1==2)
, 'hello'
and 'world'
with operators +
, ?
and :
. The parser will first attempt to add "test: "
and (1==2)
. To do this it must first evaluate the statement (1==2)
(which evaluates to false
). The +
operator causes concatenation with strings and forces all primitive variables to attempt to represent themselves as strings as well. false
evaluates as the string "false"
creating the statement "test: false"
.
The parser is now ready to evaluate the first part of the ternary: "test: false"?
. In Javascript, all non-empty strings evaluate to true
, passing the ternary operator's test and picking the first option "hello"
.
By throwing a few extra parenthesis into the original statement:
alert("test: " + ((1 == 2) ? 'hello' : 'world'));
We tell the parser that we want to evaluate the ternary operator BEFORE concatenation.
Solution 3:
Operator Precedence
All operators have what's known as precedence. This is how the language determines the order of operations. Operators with higher precedence will be evaluated before those with lower precedence. Order of operations is what allows expressions to be executed in the correct order.
For example,
1 + 2 * 3 == 1 + 6 == 7
because *
has a higher precedence than +
. Without precedence, you would get
1 + 2 * 3 == 3 * 3 == 9
+
vs. ?:
In JavaScript, the +
operator has higher precedence than the ?:
operator. This means that concatenation will take place before the condition in a ternary is evaluated. This can lead to some strange results.
Note: operator associativity and precedence can change between languages. For example, in JavaScript the ?:
operator is right associative but it's left associative in PHP. These same comparisons will produce different results between these languages.
var variable, str;
// Equality operators have higher precedence than ?: but lower than +
// so the below expression breaks down like this:
// ("A" + variable) !== undefined ? "B" : ("C" + "D")
// "Aundefined" !== undefined ? "B" : "CD"
// true ? "B" : "CD"
// "B"
str = "A" + variable !== undefined ? "B" : "C" + "D";
console.log(str);
// For the same reason as above, you get a strange result here.
// Here's how we break it down:
// ("A" + variable) === undefined ? "B" : ("C" + "D")
// "Aundefined" === undefined ? "B" : "CD"
// false ? "B" : "CD"
// "CD"
str = "A" + variable === undefined ? "B" : "C" + "D";
console.log(str);
This same type of problem will happen no matter what the condition is:
// Check for undefined
var animal;
// Expected: "The animal does not exist", actual: undefined
console.log("The animal " + animal === undefined ? "does not exist" : animal);
// Expected: "The animal undefined", actual: "does not exist"
console.log("The animal " + animal !== undefined ? "does not exist" : animal);
// Check for null
animal = null;
// Expected: "The animal does not exist", actual: null
console.log("The animal " + animal === null ? "does not exist" : animal);
// Expected: "The animal null", actual: "does not exist"
console.log("The animal " + animal !== null ? "does not exist" : animal);
// Check for property
animal = {};
// Expected: "This animal doesn't have a type", actual: undefined
console.log("The animal " + animal.hasOwnProperty('type') ? animal.type : "doesn't have a type");
animal.type = 'is a dog';
// Expected: "This animal is a dog", actual: "is a dog"
console.log("The animal " + animal.hasOwnProperty('type') ? animal.type : "doesn't have a type");
Solution
Using these same precedence rules, we know that parentheses (( ... )
) have the highest precedence of any other operators. By grouping operations inside of parentheses, those operations will be evaluated first. This works recursively, allowing you to further group operations inside of deeper sets of parentheses.
Given this, you can write your ternary expressions inside of parentheses to get the desired result.
var animal;
console.log( "The animal " + (animal === undefined ? "does not exist" : animal) );
console.log( "The animal " + (animal !== undefined ? "does not exist" : animal) );
animal = null;
console.log( "The animal " + (animal === null ? "does not exist" : animal) );
console.log("The animal " + (animal !== null ? "does not exist" : animal) );
animal = {};
console.log( "The animal " + (animal.hasOwnProperty('type') ? animal.type : "doesn't have a type") );
animal.type = 'is a dog';
console.log( "The animal " + (animal.hasOwnProperty('type') ? animal.type : "doesn't have a type") );
In general, it's a good idea to wrap your condition in parentheses as well. That way you will get the correct value when generating the condition with operators of a lower precedence.
// Assignment without parentheses
var x = 0;
console.log( x += 2 ? 'x is 2' : 'x is not 2' );
// Assignment with parentheses
x = 0;
console.log( (x += 2) ? 'x is 2' : 'x is not 2' );
Solution 4:
You need to add some extra parenthesis:
alert("test: " + ((1 == 2) ? "hello" : "world"));