Dirk Harriman Banner Image

 

Notes Javascript - Expressions & Operators


 

 

Assignment Operators

Name Shorthand Meaning
Assignment x = f() x = f()
Addition Assignment x += f() x = x + f()
Subtraction Assignment x -= f() x = x - f()
Multiplication Assignment x *= f() x = x * f()
Division Assignment x /= f() x = x / f()
Remainder Assignment x %= f() x = x % f()
Exponentiation Assignment x **= f() x = x ** f()
Left Shift Assignment x <<= f() x = x << f()
Right Shift Assignment x >>= f() x = x >> f()
Unsigned Right Shift Assignment x >>>= f() x = x >>> f()
Bitwise AND Assignment x &= f() x = x & f()
Bitwise XOR Assignment x ^= f() x = x ^ f()
Bitwise OR Assignment x |= f() x = x | f()
Logical AND Assignment x &&= f() x = x && (x = f())
Logical OR Assignment x ||= f() x = x || (x = f())
Nullish Coalescing Assignment x ??= f() x = x ?? (x = f())

 
Assigning to Properties

If an expression evaluates to an object, then the left-hand side of an assignment expression may make assignments to properties of that expression. For example:

const obj = {}; obj.x = 3; console.log(obj.x); // Prints 3. console.log(obj); // Prints { x: 3 }. const key = "y"; obj[key] = 5; console.log(obj[key]); // Prints 5. console.log(obj); // Prints { x: 3, y: 5 }.

If an expression does not evaluate to an object, then assignments to properties of that expression do not assign:

const val = 0; val.x = 3; console.log(val.x); // Prints undefined. console.log(val); // Prints 0.

In strict mode, the code above throws, because one cannot assign properties to primitives.

It is an error to assign values to unmodifiable properties or to properties of an expression without properties (null or undefined).


 

Destructuring

For more complex assignments, the destructuring assignment syntax is a JavaScript expression that makes it possible to extract data from arrays or objects using a syntax that mirrors the construction of array and object literals.

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

const foo = ["one", "two", "three"]; // WITHOUT DESTRUCTURING const one = foo[0]; const two = foo[1]; const three = foo[2]; // WITH DESTRUCTURING const [one, two, three] = foo;


 
An Example

let a, b, rest; [a, b] = [10, 20]; console.log(a); // EXPECTED OUTPUT: 10 console.log(b); // EXPECTED OUTPUT: 20 [a, b, ...rest] = [10, 20, 30, 40, 50]; console.log(rest); // EXPECTED OUTPUT: Array [30, 40, 50]


 
Description

The object and array literal expressions provide an easy way to create ad hoc packages of data.

const x = [1, 2, 3, 4, 5];

The destructuring assignment uses similar syntax, but on the left-hand side of the assignment to define what values to unpack from the sourced variable.

const x = [1, 2, 3, 4, 5]; const [y, z] = x; console.log(y); // 1 console.log(z); // 2

Similarly, you can destructure objects on the left-hand side of the assignment.

const obj = { a: 1, b: 2 }; const { a, b } = obj; // IS EQUIVALENT TO: // const a = obj.a; // const b = obj.b;

This capability is similar to features present in languages such as Perl and Python.


 
Binding and Assignment

For both object and array destructuring, there are two kinds of destructuring patterns: binding pattern and assignment pattern, with slightly different syntaxes.

In binding patterns, the pattern starts with a declaration keyword (var, let, or const). Then, each individual property must either be bound to a variable or further destructured.

const obj = { a: 1, b: { c: 2 } }; const { a, b: { c: d }, } = obj; // TWO VARIABLES ARE BOUND: `a` AND `d`

All variables share the same declaration, so if you want some variables to be re-assignable but others to be read-only, you may have to destructure twice — once with let, once with const.

const obj = { a: 1, b: { c: 2 } }; const { a } = obj; // a IS CONSTANT let { b: { c: d }, } = obj; // d IS RE-ASSIGNABLE

In assignment patterns, the pattern does not start with a keyword. Each destructured property is assigned to a target of assignment — which may either be declared beforehand with var or let, or is a property of another object — in general, anything that can appear on the left-hand side of an assignment expression.

const numbers = []; const obj = { a: 1, b: 2 }; ({ a: numbers[0], b: numbers[1] } = obj); // THE PROPERTIES `a` AND `b` ARE ASSIGNED TO PROPERTIES OF `numbers`

Note: The parentheses ( ... ) around the assignment statement are required when using object literal destructuring assignment without a declaration.

{ a, b } = { a: 1, b: 2 } is not valid stand-alone syntax, as the {a, b} on the left-hand side is considered a block and not an object literal.
However, ({ a, b } = { a: 1, b: 2 }) is valid, as is const { a, b } = { a: 1, b: 2 }.

If your coding style does not include trailing semicolons, the ( ... ) expression needs to be preceded by a semicolon, or it may be used to execute a function on the previous line.

Note that the equivalent binding pattern of the code above is not valid syntax:

const numbers = []; const obj = { a: 1, b: 2 }; const { a: numbers[0], b: numbers[1] } = obj; // This is equivalent to: // const numbers[0] = obj.a; // const numbers[1] = obj.b; // WHICH DEFINITELY IS NOT VALID.


 
Default Value

Each destructured property can have a default value. The default value is used when the property is not present, or has value undefined. It is not used if the property has value null.

const [a = 1] = []; // a is 1 const { b = 2 } = { b: undefined }; // b is 2 const { c = 2 } = { c: null }; // c is null

The default value can be any expression. It will only be evaluated when necessary.

const { b = console.log("hey") } = { b: 2 }; // DOES NOT LOG ANYTHING, BECAUSE `b` IS DEFINED AND THERE'S NO NEED // TO EVALUATE THE DEFAULT VALUE.


 
Rest Property

You can end a destructuring pattern with a rest property ...rest. This pattern will store all remaining properties of the object or array into a new object or array.

const { a, ...others } = { a: 1, b: 2, c: 3 }; console.log(others); // { b: 2, c: 3 } const [first, ...others2] = [1, 2, 3]; console.log(others2); // [2, 3]

The rest property must be the last in the pattern, and must not have a trailing comma.

const [a, ...b,] = [1, 2, 3]; // SyntaxError: REST ELEMENT MAY NOT HAVE A TRAILING COMMA // ALWAYS CONSIDER USING REST OPERATOR AS THE LAST ELEMENT


 

Destructuring Patterns With Other Syntaxes

In many syntaxes where the language binds a variable for you, you can use a destructuring pattern as well.
These include:

For features specific to array or object destructuring, please refer to the individual examples below.


 
Array Destructuring

Basic variable assignment

const foo = ["one", "two", "three"]; const [red, yellow, green] = foo; console.log(red); // "one" console.log(yellow); // "two" console.log(green); // "three"


 
Destructuring With More Elements Than The Source

In an array destructuring from an array of length N specified on the right-hand side of the assignment, if the number of variables specified on the left-hand side of the assignment is greater than N, only the first N variables are assigned values. The values of the remaining variables will be undefined.

const foo = ["one", "two"]; const [red, yellow, green, blue] = foo; console.log(red); // "one" console.log(yellow); // "two" console.log(green); // undefined console.log(blue); // undefined


 
Swapping Variables

Two variables values can be swapped in one destructuring expression.

Without destructuring assignment, swapping two values requires a temporary variable (or, in some low-level languages, the XOR-swap trick).

let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1 const arr = [1, 2, 3]; [arr[2], arr[1]] = [arr[1], arr[2]]; console.log(arr); // [1, 3, 2]


 
Parsing An Array Returned From A Function

It's always been possible to return an array from a function. Destructuring can make working with an array return value more concise.

In this example, f() returns the values [1, 2] as its output, which can be parsed in a single line with destructuring.

function f() { return [1, 2]; } const [a, b] = f(); console.log(a); // 1 console.log(b); // 2


 
Ignoring Some Returned Values

You can ignore return values that you're not interested in:

function f() { return [1, 2, 3]; } const [a, , b] = f(); console.log(a); // 1 console.log(b); // 3 const [c] = f(); console.log(c); // 1

You can also ignore all returned values:

[, ,] = f();


 
Using A Binding Pattern As The Rest Property

The rest property of array destructuring assignment can be another array or object binding pattern. This allows you to simultaneously unpack the properties and indices of arrays.

const [a, b, ...{ pop, push }] = [1, 2]; console.log(a, b); // 1 2 console.log(pop, push); // [Function pop] [Function push]

const [a, b, ...[c, d]] = [1, 2, 3, 4]; console.log(a, b, c, d); // 1 2 3 4

These binding patterns can even be nested, as long as each rest property is the last in the list.

const [a, b, ...[c, d, ...[e, f]]] = [1, 2, 3, 4, 5, 6]; console.log(a, b, c, d, e, f); // 1 2 3 4 5 6

On the other hand, object destructuring can only have an identifier as the rest property.

const { a, ...{ b } } = { a: 1, b: 2 }; // SyntaxError: `...` must be followed by an identifier in declaration contexts let a, b; ({ a, ...{ b } } = { a: 1, b: 2 }); // SyntaxError: `...` must be followed by an assignable reference in assignment contexts


 
Unpacking Values From A Regular Expression Match

When the regular expression exec() method finds a match, it returns an array containing first the entire matched portion of the string and then the portions of the string that matched each parenthesized group in the regular expression. Destructuring assignment allows you to unpack the parts out of this array easily, ignoring the full match if it is not needed.

function parseProtocol(url) { const parsedURL = /^(\w+):\/\/([^/]+)\/(.*)$/.exec(url); if (!parsedURL) { return false; } console.log(parsedURL); // ["https://developer.mozilla.org/en-US/docs/Web/JavaScript", // "https", "developer.mozilla.org", "en-US/docs/Web/JavaScript"] const [, protocol, fullhost, fullpath] = parsedURL; return protocol; } console.log( parseProtocol("https://developer.mozilla.org/en-US/docs/Web/JavaScript"), ); // "https"


 
Using Array Destructuring On Any Iterable

Array destructuring calls the iterable protocol of the right-hand side. Therefore, any iterable, not necessarily arrays, can be destructured.

const [a, b] = new Map([ [1, 2], [3, 4], ]); console.log(a, b); // [1, 2] [3, 4]

Non-iterables cannot be destructured as arrays.

const obj = { 0: "a", 1: "b", length: 2 }; const [a, b] = obj; // TypeError: obj is not iterable Iterables are only iterated until all bindings are assigned. const obj = { *[Symbol.iterator]() { for (const v of [0, 1, 2, 3]) { console.log(v); yield v; } }, }; const [a, b] = obj; // Only logs 0 and 1

The rest binding is eagerly evaluated and creates a new array, instead of using the old iterable.

const obj = { *[Symbol.iterator]() { for (const v of [0, 1, 2, 3]) { console.log(v); yield v; } }, }; const [a, b, ...rest] = obj; // Logs 0 1 2 3 console.log(rest); // [2, 3] (an array)


 

 

Object Destructuring


 
Basic assignment

const user = { id: 42, isVerified: true, }; const { id, isVerified } = user; console.log(id); // 42 console.log(isVerified); // true


 
Assigning To New Variable Names

A property can be unpacked from an object and assigned to a variable with a different name than the object property.

const o = { p: 42, q: true }; const { p: foo, q: bar } = o; console.log(foo); // 42 console.log(bar); // true

Here, for example, const { p: foo } = o takes from the object o the property named p and assigns it to a local variable named foo.


 
Assigning To New Variable Names And Providing Default Values

A property can be both

const { a: aa = 10, b: bb = 5 } = { a: 3 }; console.log(aa); // 3 console.log(bb); // 5


 
Unpacking Properties From Objects Passed As A Function Parameter

Objects passed into function parameters can also be unpacked into variables, which may then be accessed within the function body. As for object assignment, the destructuring syntax allows for the new variable to have the same name or a different name than the original property, and to assign default values for the case when the original object does not define the property.

Consider this object, which contains information about a user.

const user = { id: 42, displayName: "jdoe", fullName: { firstName: "Jane", lastName: "Doe", }, };

Here we show how to unpack a property of the passed object into a variable with the same name. The parameter value { id } indicates that the id property of the object passed to the function should be unpacked into a variable with the same name, which can then be used within the function.

function userId({ id }) { return id; } console.log(userId(user)); // 42

You can define the name of the unpacked variable. Here we unpack the property named displayName, and rename it to dname for use within the function body.

function userDisplayName({ displayName: dname }) { return dname; } console.log(userDisplayName(user)); // `jdoe`

Nested objects can also be unpacked. The example below shows the property fullname.firstName being unpacked into a variable called name.

function whois({ displayName, fullName: { firstName: name } }) { return `${displayName} is ${name}`; } console.log(whois(user)); // "jdoe is Jane"


 
Setting A Function Parameter's Default Value

Default values can be specified using =, and will be used as variable values if a specified property does not exist in the passed object.

Below we show a function where the default size is 'big', default co-ordinates are x: 0, y: 0 and default radius is 25.

function drawChart({ size = "big", coords = { x: 0, y: 0 }, radius = 25, } = {}) { console.log(size, coords, radius); // do some chart drawing } drawChart({ coords: { x: 18, y: 30 }, radius: 30, });

In the function signature for drawChart above, the destructured left-hand side has a default value of an empty object = {}.

You could have also written the function without that default. However, if you leave out that default value, the function will look for at least one argument to be supplied when invoked, whereas in its current form, you can call drawChart() without supplying any parameters. Otherwise, you need to at least supply an empty object literal.

For more information, see Default parameters > Destructured parameter with default value assignment.

Nested object and array destructuring

const metadata = { title: "Scratchpad", translations: [ { locale: "de", localizationTags: [], lastEdit: "2014-04-14T08:43:37", url: "/de/docs/Tools/Scratchpad", title: "JavaScript-Umgebung", }, ], url: "/en-US/docs/Tools/Scratchpad", }; const { title: englishTitle, // rename translations: [ { title: localeTitle, // rename }, ], } = metadata; console.log(englishTitle); // "Scratchpad" console.log(localeTitle); // "JavaScript-Umgebung"

For of iteration and destructuring

const people = [ { name: "Mike Smith", family: { mother: "Jane Smith", father: "Harry Smith", sister: "Samantha Smith", }, age: 35, }, { name: "Tom Jones", family: { mother: "Norah Jones", father: "Richard Jones", brother: "Howard Jones", }, age: 25, }, ]; for (const { name: n, family: { father: f }, } of people) { console.log(`Name: ${n}, Father: ${f}`); } // "Name: Mike Smith, Father: Harry Smith" // "Name: Tom Jones, Father: Richard Jones"


 
Computed Object Property Names And Destructuring

Computed property names, like on object literals, can be used with destructuring.

const key = "z"; const { [key]: foo } = { z: "bar" }; console.log(foo); // "bar"


 
Invalid JavaScript Identifier As A Property Name

Destructuring can be used with property names that are not valid JavaScript identifiers by providing an alternative identifier that is valid.

const foo = { "fizz-buzz": true }; const { "fizz-buzz": fizzBuzz } = foo; console.log(fizzBuzz); // true


 
Destructuring Primitive Values

Object destructuring is almost equivalent to property accessing. This means if you try to destruct a primitive value, the value will get wrapped into the corresponding wrapper object and the property is accessed on the wrapper object.

const { a, toFixed } = 1; console.log(a, toFixed); // undefined ƒ toFixed() { [native code] }

Same as accessing properties, destructuring null or undefined throws a TypeError.

const { a } = undefined; // TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined. const { a } = null; // TypeError: Cannot destructure property 'b' of 'null' as it is null.

This happens even when the pattern is empty.

const {} = null; // TypeError: Cannot destructure 'null' as it is null.


 
Combined Array and Object Destructuring

Array and Object destructuring can be combined. Say you want the third element in the array props below, and then you want the name property in the object, you can do the following:

const props = [ { id: 1, name: "Fizz" }, { id: 2, name: "Buzz" }, { id: 3, name: "FizzBuzz" }, ]; const [, , { name }] = props; console.log(name); // "FizzBuzz"


 
The Prototype Chain Is Looked Up When The Object Is Deconstructed

When deconstructing an object, if a property is not accessed in itself, it will continue to look up along the prototype chain.

const obj = { self: "123", __proto__: { prot: "456", }, }; const { self, prot } = obj; // self "123" // prot "456" (Access to the prototype chain)


 

Evaluation & Nesting

In general, assignments are used within a variable declaration (i.e., with const, let, or var) or as standalone statements.

// Declares a variable x and initializes it to the result of f(). // The result of the x = f() assignment expression is discarded. let x = f(); x = g(); // Reassigns the variable x to the result of g().

However, like other expressions, assignment expressions like x = f() evaluate into a result value. Although this result value is usually not used, it can then be used by another expression.

Chaining assignments or nesting assignments in other expressions can result in surprising behavior. For this reason, some JavaScript style guides discourage chaining or nesting assignments). Nevertheless, assignment chaining and nesting may occur sometimes, so it is important to be able to understand how they work.

By chaining or nesting an assignment expression, its result can itself be assigned to another variable. It can be logged, it can be put inside an array literal or function call, and so on.

let x; const y = (x = f()); // Or equivalently: const y = x = f(); console.log(y); // Logs the return value of the assignment x = f(). console.log(x = f()); // Logs the return value directly. // An assignment expression can be nested in any place // where expressions are generally allowed, // such as array literals' elements or as function calls' arguments. console.log([0, x = f(), 0]); console.log(f(0, x = f(), 0));

The evaluation result matches the expression to the right of the = sign in the "Meaning" column of the table above. That means that x = f() evaluates into whatever f()'s result is, x += f() evaluates into the resulting sum x + f(), x **= f() evaluates into the resulting power x ** y, and so on.

In the case of logical assignments, x &&= f(), x ||= f(), and x ??= f(), the return value is that of the logical operation without the assignment, so x && f(), x || f(), and x ?? f(), respectively.

When chaining these expressions without parentheses or other grouping operators like array literals, the assignment expressions are grouped right to left (they are right-associative), but they are evaluated left to right.

Note that, for all assignment operators other than = itself, the resulting values are always based on the operands' values before the operation.

For example, assume that the following functions f and g and the variables x and y have been declared:

function f() { console.log("F!"); return 2; } function g() { console.log("G!"); return 3; } let x, y;

Consider these three examples:

y = x = f(); y = [f(), x = g()]; x[f()] = g();

Evaluation Example 1

y = x = f() is equivalent to y = (x = f()), because the assignment operator = is right-associative. However, it evaluates from left to right:

  1. The assignment expression y = x = f() starts to evaluate.
    1. The y on this assignment's left-hand side evaluates into a reference to the variable named y.
    2. The assignment expression x = f() starts to evaluate.
      1. The x on this assignment's left-hand side evaluates into a reference to the variable named x.
      2. The function call f() prints "F!" to the console and then evaluates to the number 2.
      3. That 2 result from f() is assigned to x.
    3. The assignment expression x = f() has now finished evaluating; its result is the new value of x, which is 2.
    4. That 2 result in turn is also assigned to y.
  2. The assignment expression y = x = f() has now finished evaluating; its result is the new value of y – which happens to be 2. x and y are assigned to 2, and the console has printed "F!".

 
Evaluation Example 2

y = [ f(), x = g() ] also evaluates from left to right:

  1. The assignment expression y = [ f(), x = g() ] starts to evaluate.
    1. The y on this assignment's left-hand evaluates into a reference to the variable named y.
    2. The inner array literal [ f(), x = g() ] starts to evaluate.
      1. The function call f() prints "F!" to the console and then evaluates to the number 2.
      2. The assignment expression x = g() starts to evaluate.
        1. The x on this assignment's left-hand side evaluates into a reference to the variable named x.
        2. The function call g() prints "G!" to the console and then evaluates to the number 3.
        3. That 3 result from g() is assigned to x.
      3. The assignment expression x = g() has now finished evaluating; its result is the new value of x, which is 3. That 3 result becomes the next element in the inner array literal (after the 2 from the f()).
    3. The inner array literal [ f(), x = g() ] has now finished evaluating; its result is an array with two values: [ 2, 3 ].
    4. That [ 2, 3 ] array is now assigned to y.
  2. The assignment expression y = [ f(), x = g() ] has now finished evaluating; its result is the new value of y, which happens to be [ 2, 3 ]. x is now assigned to 3, y is now assigned to [ 2, 3 ], and the console has printed "F!" then "G!".

 
Evaluation Example 3

x[f()] = g() also evaluates from left to right. (This example assumes that x is already assigned to some object. For more information about objects, read Working with Objects.)

  1. The assignment expression x[f()] = g() starts to evaluate.
    1. The x[f()] property access on this assignment's left-hand starts to evaluate.
      1. The x in this property access evaluates into a reference to the variable named x.
      2. Then the function call f() prints "F!" to the console and then evaluates to the number 2.
    2. The x[f()] property access on this assignment has now finished evaluating; its result is a variable property reference: x[2].
    3. Then the function call g() prints "G!" to the console and then evaluates to the number 3.
    4. That 3 is now assigned to x[2]. (This step will succeed only if x is assigned to an object.)
  2. The assignment expression x[f()] = g() has now finished evaluating; its result is the new value of x[2], which happens to be 3. x[2] is now assigned to 3, and the console has printed "F!" then "G!".

 

Comparison Operators

Name Code Description
Equal (x == y) Returns true if the operands are equal.
Not Equal (x != y) Returns true if the operands are not equal.
Strict Equal (x === y) Returns true if the operands are equal and of the same type.
Strict Not Equal (x !== y) Returns true if the operands are of the same type but not equal, or are of different type.
Greater Than (x > y) Returns true if the left operand is greater than the right operand.
Greater Than Or Equal (x >= y) Returns true if the left operand is greater than or equal to the right operand.
Less Than (x > y) Returns true if the left operand is less than the right operand.
Less Than Or Equal (x > y) Returns true if the left operand is less than or equal the right operand.

 

Arithmetic Operators

An arithmetic operator takes numerical values (either literals or variables) as their operands and returns a single numerical value. The standard arithmetic operators are addition (+), subtraction (-), multiplication (*), and division (/).

These operators work as they do in most other programming languages when used with floating point numbers (in particular, note that division by zero produces Infinity). For example:

1 / 2; // 0.5 1 / 2 === 1.0 / 2.0; // THIS IS TRUE

In addition to the standard arithmetic operations (+, -, *, /), JavaScript provides the arithmetic operators listed in the following table:

Operator Desciption Example
Remainder (%) Binary operator. Returns the integer remainder of dividing the two operands. 12 % 2 // RETURNS 2
Increment (++) Unary operator. Adds one to its operand.
If used as a prefix operator (++x), returns the value of its operand after adding one;
if used as a postfix operator (x++), returns the value of its operand before adding one.
++x // PREFIX x++ // POSTFIX
Decrement (--) Unary operator. Subtracts one from its operand.
The return value is analogous to that for the increment operator.
--x // PREFIX x-- // POSTFIX
Unary Negation (-) Unary operator. Returns the negation of its operand. -x
Unary Plus (+) Unary operator. Attempts to convert the operand to a number, if it is not already. +x
Exponential Operator (**) Calculates the base to the exponent power, that is, base^exponent 2 ** 3 // 8 10 ** -1 // 0.1

 

Bitwise Operators

A bitwise operator treats their operands as a set of 32 bits (zeros and ones), rather than as decimal, hexadecimal, or octal numbers. For example, the decimal number nine has a binary representation of 1001. Bitwise operators perform their operations on such binary representations, but they return standard JavaScript numerical values.

Operator Example Desciption
Bitwise AND // 1 1 0 0 // 1 0 1 0 AND // --------- // 1 0 0 0 result = a & b; Returns a one in each bit position for which the corresponding bits of both operands are ones.
Returns a zero for each bit position where the bits are both different or both zeroes.
This is the binary operator to check to see if a bit is set or not.
Bitwise OR // 1 1 0 0 // 1 0 1 0 OR // --------- // 1 1 1 0 result = a | b; Returns a one in each bit position where both bits are one, or where one bit is a one and the other is a zero.
Returns a zero in each bit position for which the corresponding bits of both operands are zeros.
This is the binary operator to set an unset a set bit.
Bitwise XOR // 1 1 0 0 // 1 0 1 0 XOR // --------- // 0 1 1 0 result = a ^ b; Returns a zero in each bit position for which the corresponding bits are the same.
Returns a one in each bit position for which the corresponding bits are different.
This is the binary operator to toggle a bit.
Bitwise NOT // 1 1 0 0 NOT // --------- // 0 0 1 1 result = ~a; Inverts the bits of its operand.
Left Shift result = a << b; Shifts a in binary representation b bits to the left, shifting in zeros from the right.
Sign Propagating Right Shift result = a >> b; Shifts a in binary representation b bits to the right, discarding bits shifted off.
Zero Fill Right Shift result = a >>> b; Shifts a in binary representation b bits to the right, discarding bits shifted off, and shifting in zeros from the left.

 

Usage - Binary Switches

A cool trick that was used in the early days of game programming, when memory was scarce and every byte counted, was to use the bits in an integer to represent any variables that had a binary value such as on or off.

Think of a situation where you have to store millions of records where many of the data items represent a binary (off or on) value. If each item is stored in its own integer, mutiplying it by millions can really chew up a lot of memory. A solution to reduce the amount of memory used would be to store the information in single integers and using each bit to represent a binary state of a variable.

Example: Let's say that we have user records that contain multiple binary settings, perhaps user preferences:

Setting 1: On = 1 Setting 2: Off = 0 Setting 3: On = 1 ... Setting 32: Off = 0

If each were stored in its own integer and there are a million users, the necessary memory, with 32 bit integers, would be 1,000,000 x 32 x 32 = 1,024,000,000
If each setting was represented by a bit in a 32 bit integer, and there are a million users, the necessary memory would be 1,000,000 x 32 = 32,000,000

1,024,000,000 32,000,000 ------------- 992,000,000


 
Functions Used

The following is a simple example of using bits in a byte to represent a binary variable value.

<div class="row"> <div class="col-md-4"> <input type="checkbox" id="cbx_genre_1" />Piano<br/> <input type="checkbox" id="cbx_genre_2" />Guitar<br/> <input type="checkbox" id="cbx_genre_3" />Standard<br/> </div> <div class="col-md-4"> <input type="checkbox" id="cbx_genre_4" />Blues<br/> <input type="checkbox" id="cbx_genre_5" />Country<br/> <input type="checkbox" id="cbx_genre_6" />Easy Listening<br/> </div> <div class="col-md-4"> <input type="checkbox" id="cbx_genre_7" />Hard Rock<br/> <input type="checkbox" id="cbx_genre_8" />Jazz<br/> <input type="checkbox" id="cbx_genre_9" />Rock<br/> </div> </div> <a href="javascript:runscript7()" class="btn btn-bs-primary">Show Bits Set</a>

function runscript7() { let result = getMusicSettings(); let resultStr1 = ""; resultStr1 = toBinary(result, 0); resultStr2 = checkMusicSettings(result); alert("Result :"+ resultStr1 +'\n'+ resultStr2); }

// THIS FUNCTION CHECKS EACH CHECKBOX AND SETS THE // CORRESPONDING BIT IF THE CHECKBOX IS CHECKED function getMusicSettings() { let result = 0; if (document.getElementById("cbx_genre_1").checked) { result |= 1; } if (document.getElementById("cbx_genre_2").checked) { result |= 2; } if (document.getElementById("cbx_genre_3").checked) { result |= 4; } if (document.getElementById("cbx_genre_4").checked) { result |= 8; } if (document.getElementById("cbx_genre_5").checked) { result |= 16; } if (document.getElementById("cbx_genre_6").checked) { result |= 32; } if (document.getElementById("cbx_genre_7").checked) { result |= 64; } if (document.getElementById("cbx_genre_8").checked) { result |= 128; } if (document.getElementById("cbx_genre_9").checked) { result |= 256; } return result; }

// THIS FUNCTION LOOKS AT THE BITS THAT ARE SET IN THE INTEGER VALUE. // ANY BIT THAT IS SET IS ADDED TO THE RESULT STRING. function checkMusicSettings(result) { let resultStr = ""; if ((result & 1) == 1) { resultStr += "Piano Is Checked"+ '\n'; } if ((result & 2) == 2) { resultStr += "Guitar Is Checked"+ '\n'; } if ((result & 4) == 4) { resultStr += "Standard Is Checked"+ '\n'; } if ((result & 8) == 8) { resultStr += "Blues Is Checked"+ '\n'; } if ((result & 16) == 16) { resultStr += "Country Is Checked"+ '\n'; } if ((result & 32) == 32) { resultStr += "Easy Listening Is Checked"+ '\n'; } if ((result & 64) == 64) { resultStr += "Hard Rock Is Checked"+ '\n'; } if ((result & 128) == 128) { resultStr += "Jazz Is Checked"+ '\n'; } if ((result & 256) == 256) { resultStr += "Rock Is Checked"+ '\n'; } return resultStr; }

// THIS IS A HELPER FUNCTION THAT CREATES A STRING // REPRESENTATION OF A BINARY NUMBER. function toBinary(integer, withPaddingLength) { let str = integer.toString(2); return str.padStart(withPaddingLength, "0"); }

 Piano
 Guitar
 Standard
 Blues
 Country
 Easy Listening
 Hard Rock
 Jazz
 Rock

 
Show Bits Set

 

Bitwise Logical Operators

Conceptually, the bitwise logical operators work as follows:

For example, the binary representation of nine is 1001, and the binary representation of fifteen is 1111. So, when the bitwise operators are applied to these values, the results are as follows:

Expression Result Binary Description
15 & 9 9 1111 & 1001 = 1001
15 | 9 15 1111 | 1001 = 1111
15 ^ 9 6 1111 ^ 1001 = 0110
~15 -16 ~ 0000 0000 … 0000 1111 = 1111 1111 ... 1111 0000
~9 -10 ~ 0000 0000 … 0000 1001 = 1111 1111 ... 1111 0110

Note that all 32 bits are inverted using the Bitwise NOT operator, and that values with the most significant (left-most) bit set to 1 represent negative numbers (two's-complement representation).
~x evaluates to the same value that -x - 1 evaluates to.


 

Bitwise Shift Operators

The bitwise shift operators take two operands: the first is a quantity to be shifted, and the second specifies the number of bit positions by which the first operand is to be shifted. The direction of the shift operation is controlled by the operator used.

Shift operators convert their operands to thirty-two-bit integers and return a result of either type Number or BigInt: specifically, if the type of the left operand is BigInt, they return BigInt; otherwise, they return Number.

The shift operators are listed in the following table.


Bitwise Shift Operators
Operator Description Example
Left Shift
(<<)
This operator shifts the first operand the specified number of bits to the left. Excess bits shifted off to the left are discarded. Zero bits are shifted in from the right. 9<<2 yields 36, because 1001 shifted 2 bits to the left becomes 100100, which is 36.
Sign-Propagating Right Shift
(>>)
This operator shifts the first operand the specified number of bits to the right. Excess bits shifted off to the right are discarded. Copies of the leftmost bit are shifted in from the left. 9>>2 yields 2, because 1001 shifted 2 bits to the right becomes 10, which is 2. Likewise, -9>>2 yields -3, because the sign is preserved.
Zero-Fill Right Shift
(>>>)
This operator shifts the first operand the specified number of bits to the right. Excess bits shifted off to the right are discarded. Zero bits are shifted in from the left. 19>>>2 yields 4, because 10011 shifted 2 bits to the right becomes 100, which is 4. For non-negative numbers, zero-fill right shift and sign-propagating right shift yield the same result.

 

Logical Operators

Logical operators are typically used with Boolean (logical) values; when they are, they return a Boolean value. However, the && and || operators actually return the value of one of the specified operands, so if these operators are used with non-Boolean values, they may return a non-Boolean value. The logical operators are described in the following table.

Logical Operators

Operator Usage Description
Logical AND
(&&)
expr1 && expr2 Returns expr1 if it can be converted to false;
otherwise, returns expr2.
Thus, when used with Boolean values,
&& returns true if both operands are true;
otherwise, returns false.
Logical OR
(||)
expr1 || expr2 Returns expr1 if it can be converted to true; otherwise, returns expr2.
Thus, when used with Boolean values,
|| returns true if either operand is true;
if both are false, returns false.
Logical NOT
(!)
!expr Returns false if its single operand that can be converted to true;
otherwise, returns true.

Examples of expressions that can be converted to false are those that evaluate to null, 0, NaN, the empty string (""), or undefined.

The following code shows examples of the && (logical AND) operator.

const a1 = true && true; // t && t returns true const a2 = true && false; // t && f returns false const a3 = false && true; // f && t returns false const a4 = false && 3 === 4; // f && f returns false const a5 = "Cat" && "Dog"; // t && t returns Dog const a6 = false && "Cat"; // f && t returns false const a7 = "Cat" && false; // t && f returns false


The following code shows examples of the || (logical OR) operator.

const o1 = true || true; // t || t returns true const o2 = false || true; // f || t returns true const o3 = true || false; // t || f returns true const o4 = false || 3 === 4; // f || f returns false const o5 = "Cat" || "Dog"; // t || t returns Cat const o6 = false || "Cat"; // f || t returns Cat const o7 = "Cat" || false; // t || f returns Cat


The following code shows examples of the ! (logical NOT) operator.

const n1 = !true; // !t returns false const n2 = !false; // !f returns true const n3 = !"Cat"; // !t returns false


Short-Circuit Evaluation

As logical expressions are evaluated left to right, they are tested for possible "short-circuit" evaluation using the following rules:

The rules of logic guarantee that these evaluations are always correct. Note that the anything part of the above expressions is not evaluated, so any side effects of doing so do not take effect.

Note that for the second case, in modern code you can use the Nullish coalescing operator (??) that works like ||, but it only returns the second expression, when the first one is "nullish", i.e. null or undefined. It is thus the better alternative to provide defaults, when values like '' or 0 are valid values for the first expression, too.


 

BigInt Operators

Most operators that can be used between numbers can be used between BigInt values as well.

// BigInt ADDITION const a = 1n + 2n; // 3n // DIVISION WITH BigInts ROUND TOWARDS ZERO const b = 1n / 2n; // 0n // BITWISE OPERATIONS WITH BigInts DO NOT TRUNCATE EITHER SIDE const c = 40000000000000000n >> 2n; // 10000000000000000n


One exception is unsigned right shift (>>>), which is not defined for BigInt values.
This is because a BigInt does not have a fixed width, so technically it does not have a "highest bit".

const d = 8n >>> 2n; // TypeError: BigInts HAVE NO UNSIGNED RIGHT SHIFT, USE >> INSTEAD


BigInts and numbers are not mutually replaceable — you cannot mix them in calculations.

const a = 1n + 2; // TypeError: CANNOT MIX BigInt AND OTHER TYPES


This is because BigInt is neither a subset nor a superset of numbers. BigInts have higher precision than numbers when representing large integers, but cannot represent decimals, so implicit conversion on either side might lose precision. Use explicit conversion to signal whether you wish the operation to be a number operation or a BigInt one.

const a = Number(1n) + 2; // 3 const b = 1n + BigInt(2); // 3n

You can compare BigInts with numbers.

const a = 1n > 2; // false const b = 3 > 2n; // true

Also unsupported is the unary operator (+), in order to not break asm.js.

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER) // 9007199254740991n const maxPlusOne = previousMaxSafe + 1n // 9007199254740992n const theFuture = previousMaxSafe + 2n // 9007199254740993n, this works now! const multi = previousMaxSafe * 2n // 18014398509481982n const subtr = multi - 10n // 18014398509481972n const mod = multi % 10n // 2n const bigN = 2n ** 54n // 18014398509481984n bigN * -1n // -18014398509481984n

The / operator also works as expected with whole numbers, but operations with a fractional result will be truncated when used with a BigInt value, they won't return any fractional digits.

const expected = 4n / 2n // 2n const truncated = 5n / 2n // 2n, not 2.5n


Comparisons

A BigInt value is not strictly equal to a Number value, but it is loosely so:

0n === 0 // false 0n == 0 // true


 

String Operators

In addition to the comparison operators, which can be used on string values, the concatenation operator (+) concatenates two string values together, returning another string that is the union of the two operand strings.

For example,

console.log("my " + "string"); // console logs the string "my string".


The shorthand assignment operator += can also be used to concatenate strings.

For example,

let mystring = "alpha"; mystring += "bet"; // EVALUATES TO "alphabet" AND ASSIGNS THIS VALUE TO mystring.


 

Conditional (Ternary) Operators

The conditional operator is the only JavaScript operator that takes three operands. The operator can have one of two values based on a condition. The syntax is:

condition ? val1 : val2


If condition is true, the operator has the value of val1. Otherwise it has the value of val2.
You can use the conditional operator anywhere you would use a standard operator.

For example,

const status = age >= 18 ? "adult" : "minor";


This statement assigns the value "adult" to the variable status if age is eighteen or more.
Otherwise, it assigns the value "minor" to status.

 

Comma Operators

The comma operator (,) evaluates both of its operands and returns the value of the last operand. This operator is primarily used inside a for loop, to allow multiple variables to be updated each time through the loop. It is regarded bad style to use it elsewhere, when it is not necessary. Often two separate statements can and should be used instead.

For example, if a is a 2-dimensional array with 10 elements on a side, the following code uses the comma operator to update two variables at once.
The code prints the values of the diagonal elements in the array:


const x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; const a = [x, x, x, x, x]; for (let i = 0, j = 9; i <= j; i++, j--) { // ^ console.log('a[${i}][${j}]= ${a[i][j]}'); }


 

Unary Operators

A unary operation is an operation with only one operand.

delete

The delete operator deletes an object's property. The syntax is:

delete object.property; delete object[propertyKey]; delete objectName[index];


where object is the name of an object, property is an existing property, and propertyKey is a string or symbol referring to an existing property.

If the delete operator succeeds, it removes the property from the object. Trying to access it afterwards will yield undefined. The delete operator returns true if the operation is possible; it returns false if the operation is not possible.

delete Math.PI; // returns false (cannot delete non-configurable properties) const myObj = { h: 4 }; delete myObj.h; // returns true (can delete user-defined properties)


Deleting array Elements

Since arrays are just objects, it's technically possible to delete elements from them. This is however regarded as a bad practice, try to avoid it. When you delete an array property, the array length is not affected and other elements are not re-indexed. To achieve that behavior, it is much better to just overwrite the element with the value undefined. To actually manipulate the array, use the various array methods such as splice.

typeof

The typeof operator returns a string indicating the type of the unevaluated operand. operand is the string, variable, keyword, or object for which the type is to be returned. The parentheses are optional.

Suppose you define the following variables:

const myFun = new Function("5 + 2"); const shape = "round"; const size = 1; const foo = ["Apple", "Mango", "Orange"]; const today = new Date();


The typeof operator returns the following results for these variables:

typeof myFun; // returns "function" typeof shape; // returns "string" typeof size; // returns "number" typeof foo; // returns "object" typeof today; // returns "object" typeof doesntExist; // returns "undefined"


For the keywords true and null, the typeof operator returns the following results:

typeof true; // returns "boolean" typeof null; // returns "object"


For a number or string, the typeof operator returns the following results:

typeof 62; // returns "number" typeof "Hello world"; // returns "string"


For property values, the typeof operator returns the type of value the property contains:

typeof document.lastModified; // returns "string" typeof window.length; // returns "number" typeof Math.LN2; // returns "number"


For methods and functions, the typeof operator returns results as follows:

typeof blur; // returns "function" typeof eval; // returns "function" typeof parseInt; // returns "function" typeof shape.split; // returns "function"


For predefined objects, the typeof operator returns results as follows:

typeof Date; // returns "function" typeof Function; // returns "function" typeof Math; // returns "object" typeof Option; // returns "function" typeof String; // returns "function"


void

The void operator specifies an expression to be evaluated without returning a value. expression is a JavaScript expression to evaluate. The parentheses surrounding the expression are optional, but it is good style to use them to avoid precedence issues.

 

Relational Operators

A relational operator compares its operands and returns a Boolean value based on whether the comparison is true.

in

The in operator returns true if the specified property is in the specified object. The syntax is:

propNameOrNumber in objectName


where propNameOrNumber is a string, numeric, or symbol expression representing a property name or array index, and objectName is the name of an object.

The following examples show some uses of the in operator.

// Arrays const trees = ["redwood", "bay", "cedar", "oak", "maple"]; 0 in trees; // returns true 3 in trees; // returns true 6 in trees; // returns false "bay" in trees; // returns false // (you must specify the index number, not the value at that index) "length" in trees; // returns true (length is an Array property) // built-in objects "PI" in Math; // returns true const myString = new String("coral"); "length" in myString; // returns true // Custom objects const mycar = { make: "Honda", model: "Accord", year: 1998 }; "make" in mycar; // returns true "model" in mycar; // returns true


instanceof

The instanceof operator returns true if the specified object is of the specified object type. The syntax is:

objectName instanceof objectType


where objectName is the name of the object to compare to objectType, and objectType is an object type, such as Date or Array.

Use instanceof when you need to confirm the type of an object at runtime. For example, when catching exceptions, you can branch to different exception-handling code depending on the type of exception thrown.

For example, the following code uses instanceof to determine whether theDay is a Date object. Because theDay is a Date object, the statements in the if statement execute.

const theDay = new Date(1995, 12, 17); if (theDay instanceof Date) { // statements to execute }


 

Basic Expressions

All operators eventually operate on one or more basic expressions. These basic expressions include identifiers and literals, but there are a few other kinds as well. They are briefly introduced below, and their semantics are described in detail in their respective reference sections.

this

Use the this keyword to refer to the current object. In general, this refers to the calling object in a method. Use this either with the dot or the bracket notation:

this["propertyName"]; this.propertyName;


Suppose a function called validate validates an object's value property, given the object and the high and low values:

function validate(obj, lowval, hival) { if (obj.value < lowval || obj.value > hival) { console.log("Invalid Value!"); } }


You could call validate in each form element's onChange event handler, using this to pass it to the form element, as in the following example:

<p>Enter a number between 18 and 99:</p> <input type="text" name="age" size="3" onChange="validate(this, 18, 99);" />


Grouping Operator

The grouping operator ( ) controls the precedence of evaluation in expressions. For example, you can override multiplication and division first, then addition and subtraction to evaluate addition first.

const a = 1; const b = 2; const c = 3; // DEFAULT PRECEDENCE a + b * c // 7 // EVALUATED BY DEFAULT LIKE THIS a + (b * c) // 7 // NOW OVERRIDING PRECEDENCE // ADDITION BEFORE MULTIPLICATION (a + b) * c // 9 // WHICH IS EQUIVALENT TO a * c + b * c // 9


new

You can use the new operator to create an instance of a user-defined object type or of one of the built-in object types.
Use new as follows:

const objectName = new objectType(param1, param2, /* ..., */ paramN);


super

The super keyword is used to call functions on an object's parent. It is useful with classes to call the parent constructor, for example.

super(args); // calls the parent constructor. super.functionOnParent(args);


 
 

Spread Syntax

The spread (...) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

Spread syntax looks exactly like rest syntax. In a way, spread syntax is the opposite of rest syntax. Spread syntax "expands" an array into its elements, while rest syntax collects multiple elements and "condenses" them into a single element. See rest parameters and rest property.

myFunction(a, ...iterableObj, b) [1, ...iterableObj, '4', 'five', 6] { ...obj, key: 'value' }

function sum(x, y, z) { return x + y + z; } const numbers = [1, 2, 3]; console.log(sum(...numbers)); // Expected output: 6 console.log(sum.apply(null, numbers)); // Expected output: 6

Spread syntax can be used when all elements from an object or array need to be included in a new array or object, or should be applied one-by-one in a function call's arguments list. There are three distinct places that accept the spread syntax:

Although the syntax looks the same, they come with slightly different semantics.

Only iterable values, like Array and String, can be spread in array literals and argument lists.
Many objects are not iterable, including all plain objects that lack a Symbol.iterator method:

const obj = { key1: "value1" }; const array = [...obj]; // TypeError: obj is not iterable

On the other hand, spreading in object literals enumerates the own properties of the value. For typical arrays, all indices are enumerable own properties, so arrays can be spread into objects.

const array = [1, 2, 3]; const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }

All primitives can be spread in objects. Only strings have enumerable own properties, and spreading anything else doesn't create properties on the new object.

const obj = { ...true, ..."test", ...10 }; // { '0': 't', '1': 'e', '2': 's', '3': 't' }

When using spread syntax for function calls, be aware of the possibility of exceeding the JavaScript engine's argument length limit.

Examples of Spread Syntax Use

Replace apply()

It is common to use Function.prototype.apply() in cases where you want to use the elements of an array as arguments to a function.

function myFunction(x, y, z) {} const args = [0, 1, 2]; myFunction.apply(null, args); With spread syntax the above can be written as: function myFunction(x, y, z) {} const args = [0, 1, 2]; myFunction(...args);

Any argument in the argument list can use spread syntax, and the spread syntax can be used multiple times.

function myFunction(v, w, x, y, z) {} const args = [0, 1]; myFunction(-1, ...args, 2, ...[3]);

Apply For new Operator

When calling a constructor with new, it's not possible to directly use an array and apply(), because apply() calls the target function instead of constructing it, which means, among other things, that new.target will be undefined. However, an array can be easily used with new thanks to spread syntax:

const dateFields = [1970, 0, 1]; // 1 Jan 1970 const d = new Date(...dateFields);

A More Powerful Array Literal

Without spread syntax, the array literal syntax is no longer sufficient to create a new array using an existing array as one part of it. Instead, imperative code must be used using a combination of methods, including push(), splice(), concat(), etc. With spread syntax, this becomes much more succinct:

const parts = ["shoulders", "knees"]; const lyrics = ["head", ...parts, "and", "toes"]; // ["head", "shoulders", "knees", "and", "toes"]

Just like spread for argument lists, ... can be used anywhere in the array literal, and may be used more than once.

Copying An Array

You can use spread syntax to make a shallow copy of an array. Each array element retains its identity without getting copied.

const arr = [1, 2, 3]; const arr2 = [...arr]; // like arr.slice() arr2.push(4); // arr2 becomes [1, 2, 3, 4], arr remains unaffected

Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays. The same is true with Object.assign() — no native operation in JavaScript does a deep clone. The web API method structuredClone() allows deep copying values of certain supported types. See shallow copy for more details.

const a = [ [1], [2], [3] ]; const b = [...a]; b.shift().shift(); // 1 // Oh no! Now array 'a' is affected as well: console.log(a); // [[], [2], [3]]

A Better Way To Concatenate Arrays

Array.prototype.concat() is often used to concatenate an array to the end of an existing array. Without spread syntax, this is done as:

let arr1 = [0, 1, 2]; const arr2 = [3, 4, 5]; // Append all items from arr2 onto arr1 arr1 = arr1.concat(arr2); With spread syntax this becomes: let arr1 = [0, 1, 2]; const arr2 = [3, 4, 5]; arr1 = [...arr1, ...arr2]; // arr1 is now [0, 1, 2, 3, 4, 5]

Array.prototype.unshift() is often used to insert an array of values at the start of an existing array. Without spread syntax, this is done as:

const arr1 = [0, 1, 2]; const arr2 = [3, 4, 5]; // Prepend all items from arr2 onto arr1 Array.prototype.unshift.apply(arr1, arr2); console.log(arr1); // [3, 4, 5, 0, 1, 2] With spread syntax, this becomes: let arr1 = [0, 1, 2]; const arr2 = [3, 4, 5]; arr1 = [...arr2, ...arr1]; console.log(arr1); // [3, 4, 5, 0, 1, 2]

Unlike unshift(), this creates a new arr1, instead of modifying the original arr1 array in-place.


 

Short Circuiting

Consider an expression describable by the representation below, where both OP1 and OP2 are fill-in-the-blanks for OPerators.

a OP1 b OP2 c The combination above has two possible interpretations: (a OP1 b) OP2 c a OP1 (b OP2 c)

Which one the language decides to adopt depends on the identity of OP1 and OP2.
If OP1 and OP2 have different precedence levels, the operator with the higher precedence goes first and associativity does not matter. Observe how multiplication has higher precedence than addition and executed first, even though addition is written first in the code.

console.log(3 + 10 * 2); // 23 console.log(3 + (10 * 2)); // 23, because parentheses here are superfluous console.log((3 + 10) * 2); // 26, because the parentheses change the order

Within operators of the same precedence, the language groups them by associativity. Left-associativity (left-to-right) means that it is interpreted as (a OP1 b) OP2 c, while right-associativity (right-to-left) means it is interpreted as a OP1 (b OP2 c). Assignment operators are right-associative, so you can write:

a = b = 5; // same as writing a = (b = 5);

Javascript Operator Prescedence
Grouping 18 (x)
Access & Call 17 x.y x?.y x[y] new x(y) x(y) import(x)
New 16 new x
Postfix Operators 15 x++ x--
Prefix Operators 14 ++x --x !x ~x +x -x typeof x void x delete x await x
Exponentation 13 x**y
Multiplicative Operators 12 x*y x/y x%y
Additive Operators 11 x+y x-y
Bitwise Shift 10 x<<y x>>y x>>>y
Relational Operators 9 x<y x<=y x>y x>=y x in y x instanceof y
Equality Operators 8 x == y x != y x === y x !== y
Bitwise AND 7 x & y
Bitwise XOR 6 x ^ y
Bitwise OR 5 x | y
Logical AND 4 x && y
Logical OR, Nullish Coalescing 3 x || y x ?? y
Assignment & Misc 2 x = y x += y x -= y x **= y x *= y x /= y x %= y x <<= y x >>= y x >>>= y x &= y x ^= y x |= y x &&= y x ||= y x ??= y x ? y : z x => y yield x yield* x ...x
Comma Operator 1 x, y
Short Cicuiting

Short-circuiting is jargon for conditional evaluation.

For example, in the expression a && (b + c), if a is falsy, then the sub-expression (b + c) will not even get evaluated, even if it is grouped and therefore has higher precedence than &&. We could say that the logical AND operator (&&) is "short-circuited". Along with logical AND, other short-circuited operators include logical OR (||), nullish coalescing (??), and optional chaining (?.).

Truthy & Falsy

A falsy (sometimes written falsey) value is a value that is considered false when encountered in a Boolean context.

Javascript Falsy Values
Value Type Description
null Null The keyword null - The absence of any value
undefined Undefined undefined — the primitive value.
false Boolean The keyword false.
NaN Number NaN — not a number.
0 Number The Number zero, also including 0.0, 0x0, etc.
-0 Number The Number negative zero, also including -0.0, -0x0, etc.
0n BigInt The BigInt zero, also including 0x0n, etc. Note that there is no BigInt negative zero — the negation of 0n is 0n.
"" String Empty string value, also including '' and ``.
document.all Object The only falsy object in JavaScript is the built-in document.all.

In JavaScript, a truthy value is a value that is considered true when encountered in a Boolean context. All values are truthy unless they are defined as falsy. That is, all values are truthy except false, 0, -0, 0n, "", null, undefined, NaN, and document.all.

a || (b * c); // evaluate `a` first, then produce `a` if `a` is "truthy" a && (b < c); // evaluate `a` first, then produce `a` if `a` is "falsy" a ?? (b || c); // evaluate `a` first, then produce `a` if `a` is not `null` and not `undefined` a?.b.c; // evaluate `a` first, then produce `undefined` if `a` is `null` or `undefined`

When evaluating a short-circuited operator, the left operand is always evaluated.
The right operand will only be evaluated if the left operand cannot determine the result of the operation.

function A() { console.log('called A'); return false; } function B() { console.log('called B'); return false; } function C() { console.log('called C'); return true; } console.log(C() || B() && A()); // Logs: // called C // true

Only C() is evaluated, despite && having higher precedence. This does not mean that || has higher precedence in this case, it's exactly because (B() && A()) has higher precedence that causes it to be neglected as a whole. If it's re-arranged as:

console.log(A() && C() || B()); // Logs: // called A // called B // false

Then the short-circuiting effect of && would only prevent C() from being evaluated, but because A() && C() as a whole is false, B() would still be evaluated.

However, note that short-circuiting does not change the final evaluation outcome. It only affects the evaluation of operands, not how operators are grouped, if evaluation of operands doesn't have side effects (for example, logging to the console, assigning to variables, throwing an error), short-circuiting would not be observable at all.

The assignment counterparts of these operators (&&=, ||=, ??=) are short-circuited as well. They are short-circuited in a way that the assignment does not happen at all.