Operator precedence with JavaScript's ternary operator
H

7

117

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?

Huskamp answered 24/11, 2009 at 9:27 Comment(0)
O
141

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

Orthostichy answered 24/11, 2009 at 9:30 Comment(3)
I think it should be noted that, while no harm should could from h.className += ' error', it also leaves a blank space at the beginning of the string if it was initially empty. I believe the point of the ternary operation is to produce a clean-looking string.Auction
@Auction - That's exactly what I was indicating - If it's all done just to keep a space from the start, I doesn't worth it. (edge case include exact jQuery or XPath selectors). Anyway, thanks.Orthostichy
@Orthostichy +1 for the operator precedence warning alone!Toffic
G
129

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.

Gastrectomy answered 24/11, 2009 at 9:35 Comment(1)
Note sure if the perfect comment is appropriate :) It skips any explanation of why the left-hand expressions "grouped together" first (i.e. because + has greater precedence than the conditional/ternary operator (in fact the conditional operator is almost always the last one evaluated in any expression).Immersionism
F
10

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');
Fogbow answered 24/11, 2009 at 9:34 Comment(1)
well yeah, undefined isn't false it's just treated as though it wasFogbow
I
4

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');
Immotile answered 24/11, 2009 at 9:30 Comment(0)
M
3
if (h.className) {
    h.className = h.className + ' error';
} else {
    h.className = h.className + 'error';
}

should be equivalent of:

h.className += h.className ? ' error' : 'error';
Mackenziemackerel answered 24/11, 2009 at 9:34 Comment(0)
T
1

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')
Tadd answered 12/7, 2011 at 7:34 Comment(2)
The terminology here is not quite right. Precedence is inherent in the language, you do not define it. You are instead defining the order of evaluation by introducing brackets (which have higher precedence than all other operators).Immersionism
@TrueBlueAussie I accept it. i appreciate for your intent reading +1Tadd
I
1

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')
Immersionism answered 11/6, 2014 at 15:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.