Operator precedence with JavaScript's ternary operator

100.2k Views Asked by At

I can’t seem to wrap my head around the first part of this code ( += ) in combination with the ternary operator.

h.className += h.className ? ' error' : 'error'

The way I think this code works is as follows:

h.className = h.className + h.className ? ' error' : 'error'

But that isn't correct, because that gives a error in my console.

How should I interpret this code correctly?

7

There are 7 best solutions below

3
On BEST ANSWER

Use:

h.className = h.className + (h.className ? ' error' : 'error')

You want the operator to work for h.className. Better be specific about it.

Of course, no harm should come from h.className += ' error', but that's another matter.

Also, note that + has precedence over the ternary operator: JavaScript Operator Precedence

0
On

The right-hand side of the = operator is evaluated left to right. So,

g.className = h.className + h.className ? ' error' : 'error';`

is equivalent to

h.className = (h.className + h.className) ? ' error' : 'error';

To be equivalent to

h.className += h.className ? ' error' : 'error';

you have to separate the ternary statement in parentheses:

h.className = h.className + (h.className ? ' error' : 'error');
1
On

The += does what you want, but in the ternary statement at the right hand of it, it checks if h.className is falsey, which it would be if it was undefined. If it's truthy (i.e. if a class name is already specified), then error is added with a space (i.e. adding a new class), otherwise it's added without the space.

The code could be rewritten as you suggest, but you need to specify that h.className is to be used for truthiness-comparison, rather than for using its actual value, in the ternary operator, so make sure you don't bother with the concatenation of values at the same time as doing your ternary operation:

h.className = h.className + (h.className ? ' error' : 'error');
0
On
if (h.className) {
    h.className = h.className + ' error';
} else {
    h.className = h.className + 'error';
}

should be equivalent of:

h.className += h.className ? ' error' : 'error';
1
On

Think of it this way:

<variable> = <expression> ? <true clause> : <false clause>

The way the statement gets executed is basically as follows:

  1. Does <expression> evaluate to true, or does it evaluate to false?
  2. If <expression> evaluates to true, then the value of <true clause> is assigned to <variable>, <false clause> is ignored, and the next statement is executed.
  3. If <expression> evaluates to false, then <true clause> is ignored and the value of <false clause> is assigned to <variable>.

The important thing to realise with the ternary operator in this and other languages is that whatever code is in <expression> should produce a boolean result when evaluated: either true or false.

In the case of your example replace "assigned to" in my explanation with "added to", or similar for whichever shorthand arithmetic you are using, if any.

0
On

But I am not 100% happy with any of the answers as they all seem incomplete. So here we go again from first principles:

The user's overall aim:

Summarising the code: "I wish to add an error class name to a string, optionally with a leading space if there are already class names in the string."

Simplest solution

As Kobi pointed out, five years ago, having a leading space in class names will not cause any problems with any known browsers, so the shortest correct solution would actually be:

h.className += ' error';

That should have been the actual answer to the actual problem.


Be that as it may, the questions asked were...

1. Why did this work?

h.className += h.className ? ' error' : 'error'

The conditional/ternary operator works like an if statement, that assigns the result of its true or false paths to a variable.

So that code worked because it is evaluated simply as:

if (h.className IS NOT null AND IS NOT undefined AND IS NOT '')
    h.className += ' error'
else
    h.className += 'error'

2. And why did this break?

h.className = h.className + h.className ? ' error' : 'error'

The question states "that gives a[n] error in my console", which may mislead you into thinking the code does not function. In fact the following code does run, without error, but it simply returns ' error' if the string was not empty and 'error' if the string was empty and so did not meet the requirements.

That code always results in a string that contains only ' error' or 'error' because it evaluates to this pseudocode:

if ((h.className + h.className) IS NOT null AND IS NOT undefined AND IS NOT '')
    h.className = ' error'
else
    h.className = 'error'

The reason for this is that the addition operator (+ to the common folk) has higher "precedence" (6) than the conditional/ternary operator (15). I know the numbers appear backwards

Precedence simply means that each type of operator in a language is evaluated in a particular predefined order (and not just left-to-right).

Reference: JavaScript Operator Precedence

How to change the order of evaluation:

Now we know why it fails, you need to know how to make it work.

Some other answers talk about changing the precedence, but you can't. Precedence is hard-wired into the language. That is just a fixed set of rules... However, you can change the order of evaluation...

The tool in our toolbox that can change the order of evaluation is the grouping operator (aka brackets). It does this by ensuring the expressions in the brackets are evaluated before operations outside the brackets. That's all they do, but that's enough.

Brackets work simply because they (grouping operators) have higher precedence than all other operators ("there is now a level 0").

By simply adding brackets you change the order of evaluation to ensure the conditional test is performed first, before the simple string concatenation:

h.className = h.className + (h.className ? ' error' : 'error')
2
On

I would like to pick the Wayne's explanation:

<variable> = <expression> ? <true clause> : <false clause>

Let’s consider both the cases:

Case 1

h.className += h.className ? 'true' : 'false'
  • the assignment operator works fine and the value gets appended
  • when it runs for the first time, the output is false
  • Second time. Output: falsetrue -- the values keep appending

Case 2:

h.className = h.className + h.className ? 'true' : 'false'
  • the result is not same as case 1
  • when it runs for the first time, the output is false
  • Second time. Output: false -- values don't keep appending

Explanation

In the above code, case 1 works fine

whereas

case 2,

h.className = h.className + h.className ? 'true' : 'false'

is executed as

 h.className = (h.className + h.className) ? 'true' : 'false'

h.className + h.className => considered as an expression for the ternary operator as the ternary operator is given a higher precedence. So, always the result of the ternary expression is just assigned.

h.className = h.className + (h.className ? ' error' : 'error')