1. JavaScript
  2. Fundamentals
  3. Operators

JavaScript Operators

Last updated:

Comparison Operators

Equality

“Loose” Equality Operator (==)

true if the operands are equal after type conversion:

1 == 1     // true
1 == '1'   // true
1 == true  // true
0 == false // true

Using the “loose” equality operator can lead to unexpected results:

null == undefined  // true

0 == ''            // true
0 == []            // true

1 == ["1"]         // true
1 == "hello"       // true
1 == true          // true
true == "hello"    // false

Strict Equality Operator (===)

true if the operands are strictly equal with no type conversion:

1 === 1   // true
1 === '1' // false

null === undefined  // false

recommended to use strict equality comparisons whenever possible.

Not Equal Operators

1 != "1"      // false
1 !== "1"     // true
1 != "true"   // false
1 !== "true"  // true

Other Comparison Operators

const x = 1
x < 1    // false
x <= 1   // true
x > 1    // false
x >= 1   // true

Logical Operators

AND Operator

let x = 5;
let y = 10;
if (x > 0 && y > 0) {
    console.log("Both x and y are positive");
}

NB: uses short-circuit evaluation - the second operand will only be evaluated if the first operand is true. Can be useful in situations where the second operand could cause an error if it was evaluated when the first operand is false.

let x = null;

if (x !== null && x.length > 5) {
  console.log("The string is long enough");
}

OR Operator

let age = 25;
if (age < 18 || age > 60) {
    console.log("You are not eligible for this job");
}

NB: also uses short-circuit evaluation - the second operand will only be evaluated if the first operand is false. Can also be useful in situations where we want to provide a default value if a variable is undefined or null.

let x = null;
let y = x || "default value";

console.log(y);  // prints: "default value"

NOT Operator

let loggedIn = false;
if (!loggedIn) {
    console.log("Please login to continue");
}

Ternary Operator

condition ? `do something` : `do something else`
let colour = width > 100 ? "red" : "blue";

Use case - change an element class list based on state:

var el = document.getElementById("loginButton");
loginInProgress ? el.classList.add("disabled") : el.classList.remove("disabled");

Bitwise Operators

Manipulate individual bits in a number - can prove to be incredibly efficient in certain applications.

  1. AND (&) - returns a 1 in each bit position where the corresponding bits of both operands are 1; returns 0 if either bit in the compared position is 0.

     24           // binary:  0001 1000
     10           // binary:  0000 1010
    
     24 & 10 = 8  // binary:  0000 1000
    
  2. OR (|) - returns a 1 in each bit position where the corresponding bits of either or both operands are 1; returns 0 if both bits are 0.

     24           // binary:  0001 1000
     10           // binary:  0000 1010
    
     24 | 10 = 26 // binary:  0001 1010
    
  3. XOR (^) - returns a 1 in each bit position where the corresponding bits of either but not both operands are 1; returns 0 if both bits are 0 or both are 1.

     24           // binary:  0001 1000
     10           // binary:  0000 1010
    
     24 ^ 10 = 18 // binary:  0001 0010
    
  4. NOT (~) - unary operator that inverts the bits of its operand. This will also flip the sign bit (the leftmost) so the result is negative.

    NB: Bitwise operators treat their operands as a sequence of 32 bits.

     10          // binary: 00000000 00000000 00000000 00001010
     ~10 = -11   // binary: 11111111 11111111 11111111 11110101
    
  5. Left Shift (<<) - shifts the bits of the first operand to the left by the number of positions specified by the second operand - effectively a multiplication with 2 to the power of the second operand.

     10            // binary:  0000 1010
     10 << 2 = 40  // binary:  0100 1000
    
  6. Sign Propagating Right Shift (>>) - shifts the bits of the first operand to the right by the number of positions specified by the second operand. The leftmost bits are filled with the sign bit (0 for positive numbers and 1 for negative numbers).

     10            // binary:  0000 1010
     10 >> 2 = 2   // binary:  0000 0010
    
     -10           // binary:  1111 1111 1111 1111 1111 1111 1111 0110
     -10 >> 2 = -3 // binary:  1111 1111 1111 1111 1111 1111 1111 1101
    
  7. Zero-fill Right Shift (>>>) - shifts the bits of the first operand to the right by the number of positions specified by the second operand. The leftmost bits are filled with 0s.

     10            // binary:  1010
     10 >>> 2 = 2  // binary:  0010
    
     -10                     // 11111111 11111111 11111111 11110110
     -10 >>> 2 = 1073741821  // 00111111 11111111 11111111 11111101
    

Practical Examples

Fast and Efficient Odd-Even Check

Instead of using the modulo operator (%), the bitwise AND operator (&) can be used to quickly determine if a number is even or odd.

function isEven(num) {
  return !(num & 1);
}

function isOdd(num) {
  return num & 1;
}

console.log(isEven(10));  // prints: true
console.log(isOdd(5));    // prints: true

How? The bitwise AND will flip every bit to 0 with only the last one staying the same:

13           // binary:  1101
1            // binary:  0001
13 & 1 = 1   // binary:  0001

12           // binary:  1100
1            // binary:  0001
12 & 1 = 0   // binary:  0000

Flags - Extracting Specific Bits

Specific bits in a value can be used to signify features availability

function bitAtPosition(num, position) {
  return (num & (1 << position)) >> position;
}

const number = 0b1011010;  // decimal: 90
console.log(bitAtPosition(number, 3));  // prints: 1
console.log(bitAtPosition(number, 5));  // prints: 0

How?

  • 1 << position created a number with only one bit set to 1, at the position given - this is offen referred to as the mask
  • The bitwise AND is applied on the given number and the mask created above resulting in only the bit we’re interesting in being making it through
  • the bit is then moved to the right based on its position - the result will contain the required bit

Comma Operator

  • evaluate multiple expressions within a single statement
  • used to separate two or more expressions that are included where only one expression is expected, and evaluates each of its operands (from left to right)
  • returns the value of the last operand.
expr1, expr2, ... , exprN

Usage

Declaring or initialising multiple variables in a single statement:

let a = 1, b = 2, c = 3;

Variable assignment within a for loop:

for (let i = 0, j = 10; i <= 10; i++, j--) {
  console.log(`i: ${i}, j: ${j}`);
}

Multiple expressions within a single statement:

let a, b;

(a = 5, b = a * 2);

console.log(a);  // prints: 5
console.log(b);  // prints: 10

Potential Pitfalls

  • readability - overusing can make code harder to read and maintain.
  • operator precedence - has the lowest precedence among JavaScript operators.
  • side effects - expressions used with the comma operator can have unintended side effects,particularly when using functions or methods with side effects.

Unary Operators

  • operate on a single operand
  • can either precede or follow the operand

Unary Plus (+)

Used to convert its operand into a number - if the operand is already a number, it has no effect.

let x = "42";
let y = +a;
console.log(typeof y);  // Outputs: number
console.log(y);         // Outputs: 42

Unary Negation (-)

Converts non-numeric values into numbers before negating them.

let x = "42";
let y = -x;
console.log(typeof y);  // prints: number
console.log(y);         // prints: -42

Logical NOT (!)

let x = "42";
console.log(!x);  // prints: false
let y;
console.log(!y);  // prints: true

Bitwise NOT (~)

10          // binary: 00000000 00000000 00000000 00001010
~10 = -11   // binary: 11111111 11111111 11111111 11110101

Increment & Decrement

Increase or decrease the value of a numeric operand by 1. They come in two forms: prefix (++x) and postfix (x++). The difference between the two forms lies in the order of evaluation when used in an expression.

let x = 42;
let y = x++;
console.log(x);  // prints: 43
console.log(y);  // prints: 42
y = ++x;
console.log(y);  // prints: 44
console.log(y);  // prints: 44

typeof

Returns a string representing the data type of its operand.

let x = 42;
let y = "Hello, World!";
console.log(typeof x);  // prints: number
console.log(typeof y);  // prints: string

void

Evaluates an expression and returns undefined. Practical usage: when a function should not return any value (e.g. hyperlink onclick event handlers).

function doSomething() {
  console.log("Action performed");
  return "result";
}

let result = void doSomething();
console.log(result); // undefined

delete

Removes a property from an object - returns true if the deletion is successful or if the property doesn’t exist.

const person = {
    name: 'Charlie',
    age: 30
};
console.log(delete person.age);  // prints:  true

Spread Operator

Expands an iterable varable, in place, where a list of values is expected.

let colours = ['red', 'blue', 'green'];
let pastels = ['pink', 'lavender', 'peach'];

colours.push(pastels);
console.log(colours);
  // Outputs: Array(4) [ "red", "blue", "green", (3) […] ]

// vs.

colours.push(...pastels);
console.log(colours);
  // Outputs: Array(6) [ "red", "blue", "green", "pink", "lavender", "peach" ]

The spread operator is equivalent to pushing individual elements:

colours.push(pastels[0], pastels[1], pastels[2]);

Merge arrays

let colours = ['red', 'blue', 'green'];
let pastels = ['pink', 'lavender', 'peach'];
let allColours = [ ...colours, ...pastels ];
console.log(colours);
  // Outputs: Array(6) [ "red", "blue", "green", "pink", "lavender", "peach" ]

Object copy

let colour = { name: 'blue', score: 42};
let anotherColour = colour;
anotherColour.name = 'red';
console.log(colour.name);  // Outputs: 'red'

To create a copy:

let colour = { name: 'blue', score: 42};
let anotherColour = { ...colour };
anotherColour.name = 'red';
console.log(colour.name);  // Outputs: 'blue'

Combine objects

let colour = { name: 'blue', score: 42};
let apiColour = { id: 25, isActive: true};
let fullColour = { ...apiColour, ...colour };
console.log(fullColour);
  // Outputs: Object { id: 25, isActive: true, name: "blue", score: 42 }

Destructuring

The process of unpacking data from arrays or objects, into distinct variables.

Destructuring from arrays

let colours = ['red', 'blue', 'green'];
let [colour1, colour2, colour3, colour4] = colours;
console.log(colour1); // 'red'
console.log(colour2); // 'blue'
console.log(colour4); // undefined

Default values can be provides:

let colours = ['red', 'blue', 'green'];
let [colour1, colour2, colour3, colour4 = 'yellow'] = colours;
console.log(colour4); // 'yellow'

Ignore certain values when required - notice the extra comma between colour1 and colour2 in the example below:

let colours = ['red', 'blue', 'green'];
let [colour1, , colour2] = colours;
console.log(colour2); // 'green'

Destructuring assignment

Assign values to multiple variable at the same time:

let colour, score;
[colour, score] = ['blue', 42];

Destructuring from objects

Similar to the destructuring from arrays but the names of the variables to destructure to must match the name of the object properties:

let colour = { name: 'blue', score: 42};
let {name, score} = colour;
console.log(name);  // 'blue'
console.log(score); // 42

Unpack to different variable names:

let colour = { name: 'blue', score: 42};
let {name: colourName, score: colourScore} = colour;
console.log(colourName);  // 'blue'
console.log(colourScore); // 42

Default values can be supplied

let colour = { name: 'blue', score: 42};
let {id: colourId = 1, name: colourName, score: colourScore} = colour;
console.log(colourId);  // 1