reading-notes

Read 8 Notes

Home

Expressions and operators

This chapter describes JavaScript’s expressions and operators, including assignment, comparison, arithmetic, bitwise, logical, string, ternary and more.

At a high level, an expression is a valid unit of code that resolves to a value. There are two types of expressions: those that have side effects (such as assigning values) and those that purely evaluate.

The expression x = 7 is an example of the first type. This expression uses the = operator to assign the value seven to the variable x. The expression itself evaluates to 7.

The expression 3 + 4 is an example of the second type. This expression uses the + operator to add 3 and 4 together and produces a value, 7. However, if it’s not eventually part of a bigger construct (for example, a variable declaration like const z = 3 + 4), its result will be immediately discarded — this is usually a programmer mistake because the evaluation doesn’t produce any effects.

As the examples above also illustrate, all complex expressions are joined by operators, such as = and +. In this section, we will introduce the following operators:

Assignment operators Comparison operators Arithmetic operators Bitwise operators Logical operators BigInt operators String operators Conditional (ternary) operator Comma operator Unary operators Relational operators These operators join operands either formed by higher-precedence operators or one of the basic expressions. A complete and detailed list of operators and expressions is also available in the reference.

The precedence of operators determines the order they are applied when evaluating an expression. For example:

const x = 1 + 2 * 3; const y = 2 * 3 + 1;

Despite * and + coming in different orders, both expressions would result in 7 because * has precedence over +, so the *-joined expression will always be evaluated first. You can override operator precedence by using parentheses (which creates a grouped expression — the basic expression). To see a complete table of operator precedence as well as various caveats, see the Operator Precedence Reference page.

JavaScript has both binary and unary operators, and one special ternary operator, the conditional operator. A binary operator requires two operands, one before the operator and one after the operator:

operand1 operator operand2 For example, 3 + 4 or x * y. This form is called an infix binary operator, because the operator is placed between two operands. All binary operators in JavaScript are infix.

A unary operator requires a single operand, either before or after the operator:

operator operand operand operator For example, x++ or ++x. The operator operand form is called a postfix unary operator, and the operand operator form is called a prefix unary operator. ++ and – are the only postfix operators in JavaScript — all other operators, like !, typeof, etc. are prefix.

Assignment operators An assignment operator assigns a value to its left operand based on the value of its right operand. The simple assignment operator is equal (=), which assigns the value of its right operand to its left operand. That is, x = f() is an assignment expression that assigns the value of f() to x.

There are also compound assignment operators that are shorthand for the operations listed in the following table:

Name Shorthand operator 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 = f()) Logical OR assignment x ||= f() x || (x = f()) Logical nullish assignment x ??= f() 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 }.

For more information about objects, read Working with Objects.

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.

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;

Evaluation and 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()