Even if you’ve been using JavaScript for many years, you probably still have surprising moments where you come across areas that are still not completely understandable.

This post is meant to shed some light on some of the most common areas of confusion, including logical operators, the equality operator, semicolons, etc.

Types

To better understand this post, it’s necessary to clearly understand the possible value types used in JavaScript.

Here’s a brief overview:

  • Undefined: This type has exactly one value, which is called undefined . Any variable that has not been assign a value has the value undefined .
  • Null: This type has exactly one value, which is called null. It is used to represent a missing object.
  • Boolean: It has two logical values – true andfalse.
  • String: A set of ordered sequences of 16-bit characters, which are used to represent textual data.
  • Number: JavaScript has a single type of numbers – they are always 64-bit floating point. This type also includes the values NaN, +Infinity and -Infinity.
  • Object: An object is a collection of properties.

All of the above types are used when speaking about primitive data types, except for object. 

We can use the typeof operator to get information about the operand, which will return a string containing the name of the type.

Just be aware that for the null value it returns 'object' (since null occurs where we expect an object). In the case of functions (which are objects), it will return 'function'.

Logical Operators

The operators logical AND (&&), logical OR (||), and logical NOT (!) are typically used for boolean type values (logical) – and when they are, they return Boolean values.

But what about when operators are applied to non-Boolean values?

'Tom' && 'Jerry' // returns 'Jerry'
'Mickey' || 'Minnie' // returns 'Mickey'
null || 0 // returns 0
undefined && false // returns undefined
!!'hello' // returns true

Logical operators follow these rules:

  • The && operator returns the first operand if it can be converted to false, otherwise it returns the second operand.
  • The || operator returns the first operand if it can be converted to true, otherwise it returns the second operand.
  • The operator ! returns false if the operand can be converted to true, otherwise it returns true.

The || operator is a popularly used as a shortcut in control structures like the following for foo = opt_foo || create_foo():

if (opt_foo)
  foo = opt_foo
else
  foo = create_foo()

The ! operator is sometimes misused to convert any value to a boolean value. For example,  !!0 === false is easier to read as Boolean(0) === false.

Logical Values

In the previous seciton, we converted values to a value of type boolean. So, let’s see how this conversion works, i.e. what the result of Boolean(expr)is.

The result is false if the evaluated expr takes one of the following values: undefinednull, false, 0, -0, NaN, or an empty string ''.

In other cases (e.g. '0', 'false', [], ' ', or any object like new Boolean(false)) the result will be true.

The conversion to logical values is very common, not only in the cases that have been mentioned here, but also with ifwhile or the conditional operator (?:) as well.

Equality Operators

The equality operator == always returns a value of type boolean. It’s used to tell whether two operands are of equal value. If they are equal, then it returns a value of true.  And if they aren’t equal, then it returns a value of false .

But how is equality evaluated?

If the operands are of the same type, the rule is simple: the values must match (with the exception of NaN , which is not equal to itself or anything else, and  +0 is equal to -0) and in the case of the object, the values must be reference to the same object.

NaN != NaN
0 == -0
{} != {}

When values of the same type don’t match, the operator evaluation seems magical.

Take a look for yourself:

1.

null == undefined

2.

0 == ' rn'
'0' == false
' 0x01' == true
'2' != true
'2' != false
'true' != true
'true' != false
0 == []
0 == [0]
0 != {}
2 == [2]
false == []
true != {}
false != {}

3.

'0' == [0]
'2' == [2]
'' == []
'' != {}

4.

0 != null
null != true
null != false
'null' != null
'undefined' != undefined
undefined != true
undefined != false

Javascript uses the following rules to determine the result:

  1. null equals undefined.
  2. If one operand has type number or boolean, and the second number, booleanstring, or object, then both operands are converted to numbers.
  3. If one operand has type string and the second object, then the second operand is converted to a string. Object’s method valueOf method or toString is used to do this (toString is used if valueOf doesn’t return a value of primitive type or isn’t defined at all). For example, the result of a native Array  method toString will return the same results as join(',').
  4. In other cases, inequality occurs.

Additionally, the equality operator is not transitive. For example: [1] == 1 and 1 == new Boolean('0'), but [1] != new Boolean('0'). However, a == b, then b == a is always true.

It’s also important to cover how values are converted to numbers.

Let’s start with a few examples:

Number(true) // 1
Number(false) // 0
Number(' rn') // 0
Number('true') // NaN
Number('125 1') // NaN
Number(' 5e1 ') // 50
Number([]) // 0
Number([2]) // 2
Number({}) // NaN
Number({valueOf: function() {return 1}}) // 1
Number(undefined) // NaN
Number(null) // 0
  • boolean – The values are converted to 0 and 1.
  • string – If a string contains a numeric literal (whitespaces before or after are ignored), the result is a number. Otherwise, the result value is NaN. If the literal is empty (i.e. the original string is empty or contains only whitespace), the result is 0.
  • object – The valueOf method (or if necessary, the toString method) is used to convert it to a primitive value.

It goes without question that you need to be very careful when using the equality operator, since there’s a large margin for errors which will be difficult to figure out.

It’s also important to note the difference between if (!a) and if (a == false) and also between, if (a) and if (a == true). In both cases, you should use the first option not the == operator if you want to test a logical value.

Otherwise, it’s generally recommended to use the strict equality operator ===, which returns false if the types of operands differ. If you’re curious, CoffeeScript compiles its == operator in Javascript as the === operator.

You can also force the same type of comparison with the equality operator like this:

  • Convert to string before comparing: '' + a == '' + b
  • Convert to number before comparing: +a == +b
  • Convert to boolean before comparing: !a == !b

Semicolons

Thanks to Javascript’s automatic semicolon insertion, we’re not required to write a semicolon after each statement.

If you decide to leave the task of inserting a semicolon to the interpreter, you’ll need to watch out for places where semicolons aren’t inserted automatically because it will still make sense as a single statement without the semicolon.

For instance, we often see a similar problem when combining multiple Javascript files into one:

MyClass.prototype.myMethod = function() {
  return 42
}

(function() {
  // Initialization in the scope of anonymous function
})()

Simply put, Javascript always inserts semicolons if the parser encounters an invalid token that isn’t allowed by the grammar – but this only applies at line breaks and before closing curly brackets.

To put it another way, Javascript won’t put a semicolon at the end of a line if the token on the following line satisfies the language grammar (or doesn’t, but a semicolon won’t fix it).

More specifically, a semicolon is inserted if:

  1. The statement has an unclosed parenthesis, array literal, object literal, or ends in some other way that is not a valid way to end a statement. For instance, ending with . or ,.
  2. The line ends with for (), while (), doif (), or else.
  3. If a line only contains a -- or ++ (in which case it is interpreted as a prefix decrement/increment operator).
  4. The next line begins with [, (, +, */, -, , , ., or any other binary operator.

Below are some examples of these individual rules:

1.

var a, // Semicolon will not be inserted 
b, c

2.

if (a)
do_something() // This function will be called only if condition a is evaluated as true

3.

i // Semicolon will be inserted here as a result of "restricted production" - see below
++ // Semicolon will not be inserted
j // j value will be incremented

4.

foo()
[1,2,3].forEach(bar) // TypeError if at index 3 of return value of foo is not array

JavaScript grammar has “restricted productions”, which restrict the use of a line terminator in certain places. Specifically, before the postfix operator, or after continue, breakreturn, throw.

Take a look at the following code:

return
    a + b;

A semicolon is automatically inserted after the return statement and function returns undefined instead of a + b. Even if you write semicolons after every statement, you won’t be protected from this type of mistake.

The Addition Operator

The addition operator (+) is worth mentioning because of its dual functionality:

  • adds numeric value to another
  • concatenates two strings

If you don’t keep an eye on value types, you can end up being surprised by the results 1 + '1' === '11'.

This is how the addition operator works:

  1. If the operand is an object, then it’s converted to a primitive type (see the first section).
  2. If at least one of the operands is a string, then the second is converted to a string, and the results is a connection of the two strings. 
  3. In other cases, the operands are converted to numbers and then counted as numbers.

Thus, it’s clear why [] + [] returns an empty string and [] + {} is evaluated to string  '[object Object]'.

Final Thoughts 

While I was writing this article, I came across a lightning talk with the following expressions evaluated mysteriously:

{} //evaluates as undefined
{} + '1' //evaluates as 1
{} + [] //evaluates as 0
{} + {} //evaluates as NaN

For understanding, it’s enough to know that {} can be parsed (according to context) as an empty block or as an expression – a object literal. Empty blocks return an empty value, and therefore, the statement {} + '1' is equivalent to +'1', the result of which is the number 1. The rest of the cases are left as an exercise for the reader.

If you wanted to force {} to be parsed as an expression, you would simply enclose it in parentheses, so ({} + '1') would be evaluated as '[object Object]1'.

After reading this article, you could try to explain the values in these tables as an easy exercise.

More questions or comments? 

Share them with us here or on Facebook. You can also tweet Jan directly on Twitter

This article was originally published in Czech on Zdrojak by Testomato developerJan Prachar. It has been translated and posted here with his permission.