If you are a web developer like me, you deal with JavaScript code every day. However, despite using this library in our daily work, many of us do not know all of the JavaScript expressions and operators that would help make our lives easier.
In this article, I have listed some must-know expressions and operators in JavaScript, including simple and concise examples for each. Let’s dive in!
An expression is any valid unit of code (made of a set of literals, variables, operators, and other simpler expressions) that produces a value when resolved. An expression can be as simple as a variable name that equals whatever value we assigned to it (like x = 5).
As you likely already know, the value we assign to our variable can be anything from a number, to a string, to a boolean.
There are 👇🏼 five categories of expressions; the first three are fairly simple, while the last two are a little more complex.
These expressions use arithmetic operators, numbers like 5 or 5.864.
These expressions have a set of characters like "nada"
or "5.864"
as values.
These expressions equal true or false, usually through a logical operator such as &&
or ||
.
These are basic keywords and key characters we use in our JavaScript code (you probably know most of these):
this
:As in this.propertyName
. The this
expression refers to an object’s property within an execution context.
… Now you might be wondering: what is the execution context for this
? Generally, it is the global context (in a browser, for example, it would be window
). An exception would be if it is used within an object method (e.g. user.fullName()
). In this case, this
is called within the method (fullname()
) and will refer to the object context (user
).
For more info, codeSTACKr explains this
in more details in this video.👈🏼
function
, function*
and async
function:As you know, function
defines the function expression (duh). Stating the obvious, a function is a code procedure that takes an input (a set of statements) and returns an output in the form of a task performance.
On the other hand, a function*
defines a generator function, which simplifies the task of writing iterators by generating a sequence of results instead of a single value.
Although you can use function*
in asynchronous programming, it’s better to use an async
function simply. An async function enables “asynchronous, promise-based behavior to be written in a cleaner style,” while avoiding the need to configure promise chains explicitly.
Generally speaking, an async function will allow you to perform a series of tasks without having to wait for one to finish before running the next one.
For more info, I recommend checking out this video.
yield
, yield*
and await
:To start, let’s differentiate yield
from return
and yield
from await
:
return
is used in regular functions, while yield
is used in generator functions (function*
). The difference is that in a function, we can simply return
a value. In contrast, in a generator function, we generate a sequence of values, so yield
is used to generate multiple values until we stop calling that function*
.
On the other hand, await
is only used in async
functions. The sole mission of an async
function is to return a promise, so await
will call Promise.resolve
on the awaited value.
Now that we have differentiated between return
, yield
and await
, you might be wondering what the heck is yield*
about. It’s actually fairly simple: yield*
delegates to another generator function in the following manner:
function* function1() { yield "I'm the value from function1 👋 but when function2 is called, it delegates to function1 the task of generating a value"; } function* function2() { yield* function1(); } console.log(function2().next().value); // expected output: "I'm the value from function1, but when function2 is called, it delegates to function1 the task of generating a value "
class
:A user on Quora described a class as “a blueprint for an object,” and I couldn’t agree more with this comparison.
To wrap your brain around the concept of a class expression (introduced in ES6), it’s useful to see how it works in an example:
class ClothingItem { constructor(type, season) { this.type = type; this.season = season; } description() { return `This ${this.type} is for ${this.season}`; } } console.log(new ClothingItem("dress", "winter")); // expected output: Object {season: "winter", type: "dress"} console.log(new ClothingItem("dress", "winter").description()); // expected output: "This dress is for winter"
As shown here, after we defined the object’s instance properties using the constructor()
, we were able to bind our data to a method using description()
.
[]
:There are different ways to initialize an array, but the simplest way is by using []
:
let myEmptyArray = []; console.log(myEmptyArray); // expected output: []
You can then push array elements into it (myEmptyArray.push(475)
) or even define them at the initialization phase (let myArray = [1, 100]
).
{}
:Similar to the same way we can initialize an array with a literal syntax instead of the constructor syntax, we can also initialize an object with just {}
:
let myEmptyObject = {}; console.log(myEmptyObject); // expected output: Object {}
/ab+c/i
:RegExp is used to match text with a pattern, making sure that what the user puts in a field matches the pattern on, say, an email or a number, for instance).
A while ago, I found this great tool to learn, build, and test RegExp. But, for a quick cheat sheet that helps me get the regular expressions that I need quickly, I use iHateRegex 😉.
()
:The parentheses that we call the grouping operator simply control the precedence of evaluation in any given expression.
As you know, 1 + 2 * 3
will yield the same result as 1 + (2 * 3)
(7). However, if you change the order of the parenthesis, you change who gets to be evaluated first. For example, (1 + 2) * 3
will return 9.
As a programmer, this comes in handy in situations where you need to evaluate many conditions using ternary operators:
condition1 ? "statement 1" : (condition2 ? "statement 2" : "statement 3");
Left-hand-side (LHS) expressions refer to the location of a particular expression or assignment. Unsurprisingly, you’ll find them on the left-hand-side of the code block. They are comprised of the following:
A property accessor provides us with a way to access an object property by using either of these two syntaxes:
object.property
object["property"]
Check out the example below:
const myObject = { firstObject: "Boku", secondObject: "Anata", }; console.log(myObject.firstObject); // Expected output: "Boku" console.log(myObject["secondObject"]); // Expected output: "Anata"
new
:As we saw in our earlier example of [class]
expressions, you can create an instance of an object just using the new
keyword. Read more about the details of the new
operator here.
new.target
:new.target
simply detects if a function or constructor was called using the new
keyword. Learn more about this meta property in this video and in this article. 👈🏻
super
:The keyword super
is used to access and call a parent constructor. It can come in handy with class inheritance when, for instance, you have two constructors that share common parts. To avoid duplicating your code, you can call super()
.
Here’s an example of super
at work:
class Movie { constructor(name, year) { this.name = name; this.year = year; } MovieDescription() { return `Movie: ${this.name}, year: ${this.year}.`; } } console.log(new Movie("Ma Rainey's Black Bottom", "2020")); // expected output: Object { name: "Ma Rainey's Black Bottom", year: "2020"} console.log(new Movie("Ma Rainey's Black Bottom", "2020").MovieDescription()); // expected output: "Movie: Ma Rainey's Black Bottom, year: 2020." class TvShow extends Movie { constructor(name, year, seasons) { super(name, year); this.seasons = seasons; } TvShowDescription() { return `Tv Show: ${this.name}, number of seasons: ${this.seasons}, year: ${this.year}.`; } } console.log(new TvShow("F.R.I.E.N.D.S", "1994", 10)); // expected output: Object { name: "F.R.I.E.N.D.S", seasons: 10, year: "1994"} console.log(new TvShow("F.R.I.E.N.D.S", "1994", 10).TvShowDescription()); // expected output: "Tv Show: F.R.I.E.N.D.S, number of seasons: 10, year: 1994."
...obj
:The spread syntax, ...
, allows you to expand an expression. For instance, if you need to add an array into an array, you may get something like this (if you don’t use the ...
): [a, [b, c], d]
.
One way that you can use the spread operator is to spread the array elements:
let childArray = ["b", "c"]; let parentArray = ["a", ...childArray, "d"]; console.log(parentArray); // expected output: [a, b, c, d]
There are a few other ways the spread syntax can be used, covered in this article.
Now that we’ve seen what expressions can do, it’s time to jump into talking about operators. Operators are used to build complex expressions entirely out of simpler expressions. We’ll explain more below.
Operators are the tools we use to generate right-hand-side (RHS) values. They can be as simple as the addition operator — a + b = c
where the generated right-hand-value is c — or a bit trickier where a conditional operator, for instance, is used: (c > a) ? "c is greater than a": "c is not greater than a"
.
There are three types of operators: unary, binary, and ternary. In the following sections, we’ll discuss all three with simple, easy-to-follow examples.
A unary operator is an operator that only requires one operand (expression) to generate a value. For example, in 2++
I only need one operand (2
) to generate a value.
There are many types of unary operators, which we will discuss below.
++
:The increment operator is pretty straightforward: it adds 1. However, take note that its behavior varies depending on whether it postfixes or prefixes its operand:
let a = 2; console.log(a++); // expected output: 2 console.log(a); // expected output: 3 let b = 2; console.log(++b); // expected output: 3 console.log(b); // expected output: 3
--
:The same principle as the increment operator applies to the decrement operator:
let a = 2; console.log(a--); // expected output: 2 console.log(a); // expected output: 1 let b = 2; console.log(--b); // expected output: 1 console.log(b); // expected output: 1
+
:The unary plus operator +
does one simple thing: it converts its operand to a number (if it is not one already):
let a = "2"; console.log(a); // expected output: "2" console.log(+a); // expected output: 2
This trick is handy to cast a string into a number. You might ask: what if it can’t be converted to a number? In that case, +"some_string"
returns NaN
.
-
:The unary negation operator does the same thing as +
(converts a string to a number), but it goes the extra mile by also negating its operand:
let a = "2"; console.log(a); // expected output: "2" console.log(-a); // expected output: -2
A logical operator is an operator used with logical values, or as we commonly know them: Booleans (true/false). Therefore, it follows that a unary logical operator is an operator that only needs one boolean operand to generate a value.
!
:The !
operator returns false
when applied to a truthy
expression, 👉🏼 and vice versa.
let a = 2; let b = 4; console.log(a < b); // expected output: true console.log(!(a < b)); // expected output: false console.log(!(a > b)); // expected output: true console.log(!"truthy"); // expected output: false console.log(!"truthy"); // expected output: false
As human beings, we make sense of numbers using the decimal system (1, 4.5, 5000, and so on). Computers, on the other, process numbers in a binary format (a combination of zeroes and ones).
What bitwise operator does is evaluate the operand, not based on their decimal value, but instead based on their binary 32-bit representation:
let decimal = 9; let binary = decimal.toString(2); console.log(binary); // expected output: "1001" // 32 bit integer : "00000000000000000000000000001001"
Fortunately, this 32-bit representation happens behind the curtains. The output of the bitwise operator is still a standard JavaScript output, as we’ll cover below.
~
:The unary bitwise NOT operator (~
) inverts the bits of its operand.
const a = 3; // 32-bit integer: 00000000000000000000000000000011 console.log(~a); // expected output: -4 // 32-bit integer: 11111111111111111111111111111100
What happens here is that the NOT operator takes our operand’s (3
) 32-bit representation 00000000000000000000000000000011
, reverts the zeros to ones, and the reverts the ones into zeros.
To convert a decimal to a binary or 32-bit integer, check out this useful tool.
delete
operator:You guessed it: this operator removes the operand that it is applied to, as long as the property belongs to an object (including arrays):
const programmer = { alias: "rosen", age: 30, }; console.log(programmer.alias); // expected output: "rosen" delete programmer.alias; console.log(programmer.alias); // expected output: undefined
Note, however, that you cannot use delete
on an ordinary variable.
const programmer = "rosen"; console.log(programmer); // expected output: "rosen" delete programmer; console.log(programmer); // expected output: "rosen"
void
operator:If, for some reason, you need an expression to return undefined (even though it’s supposed to return something), the way to go is the void
operator.
function notVoid() { return "I am not void!"; } console.log(notVoid()); // expected output: "I am not void!" console.log(void notVoid()); // expected output: undefined
typeof
operator:Finally, as its name implies, the typeof
operator names the type of expression that it is applied to:
console.log(typeof 3); // expected output: "number" console.log(typeof "3"); // expected output: "string" console.log(typeof (3 > "3")); // expected output: "string" function three() {} console.log(typeof three); // expected output: "function" array = []; console.log(typeof array); // expected output: "object"
In contrast to unary operators, binary operators require two operands to generate a value.
For example, the comparison operator greater than( >
) can only generate a value (true
or false
) if it is applied to two expressions (in this instance, 2 > 5
will evaluate to false
).
+
:let a = 4; let b = 2; console.log(a + b); // expected output: 6
-
:let a = 4; let b = 2; console.log(a - b); // expected output: 2
/
:let a = 4; let b = 2; console.log(a / b); // expected output: 2
*
:let a = 4; let b = 2; console.log(a * b); // expected output: 8
**
:The exponentiation operator calculates the exponent to the base. In the example below, you will that 4 is the base and 2 is the exponent, resulting in an expected output of 16.
let a = 4; let b = 2; console.log(a ** b); // expected output: 16
%
:Also called modulus, the remainder ( %
)operator returns the “leftover” from the division of two operands.
let a = 4; let b = 2; console.log(a % b); // expected output: 0 let c = 3; console.log(a % c); // expected output: 1
As the name suggests, comparison operators compare the operands they are applied to and then return a true
or false
.
Note that you can compare any operand, whether it be a number, a string, a boolean, or an object. Strings, for example, are compared based on their unicode values. In situations where we are comparing operands of different types, JavaScript will convert the operands to compatible types for comparison.
string = "string"; console.log(string.charCodeAt()); // returns a string unicode value // expected value: 115 console.log(string < 3); // expected value: false console.log(false > true); // expected value: false console.log(true > false); // expected value: true function operand1() { return "hello"; } bye = ["zaijian", "matta", "besslama", "tchao"]; console.log(operand1() !== bye); // expected value: true
There are four different kinds of equality operators: ==
, !=
, ===
, and !==
. In the following examples, we’ll show exactly how each works, but to start, here are a few notes to keep in mind:
==
and not equal !=
operators convert the operands before comparing them, so 3 == "3"
evaluates to true
, even though we’re comparing a number and a string.===
and strict not equal !==
operators will consider the type of the operand it compares. Thus, 3 === "3"
will return false
in this case.==
:console.log(3 == "3"); // expected value: true console.log(3 == 3); // expected value: true
!=
:console.log(3 != "3"); // expected value: false console.log(3 != 3); // expected value: false
===
:console.log(3 === "3"); // expected value: false console.log(3 === 3); // expected value: true
!==
:console.log(3 === "3"); // expected value: true console.log(3 === 3); // expected value: false
>
:console.log(3 > 1); // expected value: true
>=
:console.log(3 >= "3"); // expected value: true
<
:console.log("3" < 1); // expected value: false
<=
:console.log(3 <= 1); // expected value: false
&&
:The &&
operator must evaluate both of its operands before returning true
or false
. This also means that if only one of the expressions is false
, the AND will return false
.
console.log(3 > 1 && "3" > 0); // expected value: true
||
:On the other hand, the ||
operator will return true
if either of its operands is true
. Therefore, if the first operand evaluates to true
, then the expected value will return as true
without needing to check the second operand.
console.log(3 > 1 || "3" == 0); // expected value: true
As discussed earlier in this guide, bitwise operators evaluate their operands based on their 32-bit representation; the value is then returned in standard JavaScript output.
For more a more in-depth discussion of use cases for JavaScript bit operators, I recommend reading this article.
&
:The bitwise AND operator (&
) puts a 0 in its evaluation result when one of the 32 bits in each of the two operands binary representation have opposite values (0 versus 1):
const a = 3; // 32-bit integer: 00000000000000000000000000000011 const b = 7; // 32-bit integer: 00000000000000000000000000000111 console.log(a & b); // expected output: 3 // 32-bit integer: 00000000000000000000000000000011
As you can see here, the 1
in b
‘s binary representation, which conflicts with the 0
in a
‘s binary representation at that same position, has been inverted to 0
.
^
:The bitwise XOR operator (^
) follows quite a different logic than the bitwise &
operator. Unlike the latter, ^
only reverts the 1
‘s (in the same position) in each of the two operands binary to 0
‘s:
const a = 3; // 32-bit integer: 00000000000000000000000000000011 const b = 7; // 32-bit integer: 00000000000000000000000000000111 console.log(a ^ b); // expected output: 4 // 32-bit integer: 00000000000000000000000000000100
|
:The bitwise OR operator (|
) follows the same logic as &
EXCEPT for reverting the bit to 1 (instead of 0).
const a = 3; // 32-bit integer: 00000000000000000000000000000011 const b = 7; // 32-bit integer: 00000000000000000000000000000111 console.log(a | b); // expected output: 7 // 32-bit integer: 00000000000000000000000000000111
In our previous examples, we have seen how bitwise logical operators take the 32-bits of their operands, evaluate them, and output a result where they revert some bit’s value.
On the other hand, bitwise shift operators take the 32-bits binary representation of its LHS operand and shifts one bit to a specific position (specified by its RHS operand).
To better visualize how this and each shift operator works, let’s walk through the examples below:
<<
:Here, the left shift operator takes a
‘s 32-bits binary representation, shifts by 7 (b
) positions to the left, and discards the excess (000000).
const a = 3; // 32-bit integer: 00000000000000000000000000000011 const b = 7; console.log(a << b); // expected output: 384 // 32-bit integer: 00000000000000000000000110000000
>>
:The right shift operator >>
does the same as the left shift operator <<
, but with two differences:
const a = 5; // 32-bit integer: 00000000000000000000000000000101 const b = 2; console.log(a >> b); // expected output: 1 // 32-bit integer: 00000000000000000000000000000001 const c = -5; // 32-bit integer: -00000000000000000000000000000101 console.log(c >> b); // expected output: -2 // 32-bit integer: -00000000000000001111111111111110
>>>
:Unsigned (zero-fill) right shift operator >>>
, shifts a
‘s 32-bits binary representation by b
(1 position) to the right, similar to the >>
operator. The major difference here is that >>>
can make negative numbers into positive ones, like so:
const a = 3; // 32-bit integer: 00000000000000000000000000000011 const b = 1; console.log(a >>> b); // expected output: 1 // 32-bit integer: 00000000000000000000000000000001 const c = -3; // 32-bit integer: -00000000000000000000000000000011 console.log(c >>> b); // expected output: 2147483646 // 32-bit integer: 01111111111111111111111111111110
?.
:Like many developers, you’ve likely tried to get a value deep within an object’s chain, but because it was null or undefined, it caused an error.
Instead of using the chaining operator .
in intricate objects, you may instead choose to go with the optional chaining operator ?.
next time. This operator allows you to search for a value without validating every reference within the chain.
const holiday = { name: "christmas", companions: "family", travel: {}, }; console.log(holiday.travel.country); // expected output: undefined // Causes errors console.log(holiday?.travel.country); // expected output: undefined // Only returns undefined
,
:Of course we all know our beloved ,
operator, but it can’t hurt to refresh our memory! Comma operators separate both variable declarations (e.g. a = [6, 3, 1, 8]
) and expressions so that they can be executed in order (like in loop variable declarations: var i = 0; i < 100; i++
).
in
operator:The in
operator returns true if the object has the given property the operator is looking for.
const holiday = { name: "christmas", companions: "family", travel: {}, }; console.log("name" in holiday); // expected output: true
instanceof
operator:If you want to confirm that a property is an instance of a particular operator, you can use instanceof
as so:
class ClothingItem { constructor(type, season) { this.type = type; this.season = season; } } const clothes = new ClothingItem("dress", "winter"); console.log(clothes instanceof ClothingItem); // expected output: true console.log(clothes instanceof Object); // expected output: true
As the name suggests, an assignment operator assigns a value (based on its RHS operand) to its LHS operand.
=
:The primary assignment operator consists of the equal sign that assigns b to a in a = b
.
a = 1; b = 4; console.log(a); // expected output: 1 a = b; console.log(a); // expected output: 4
[a, b] = [1, 2]
, {a, b} = {a:1, b:2}
):The destructuring assignment syntax makes it possible to first extract data from arrays or objects, and then assign that data to distinct variables:
const array = [6, 3, 1, 8]; const [a, b, c, d] = array; console.log([a, b, c, d]); // expected output: [6, 3, 1, 8] console.log(a); // expected output: 6 const object = { first: 6, second: 3, third: 1, fourth: 8, }; const { first, second, third: e, fourth } = object; console.log({ first, second, e, fourth }); // expected output: Object { // e: 1, // first: 6, // fourth: 8, // second: 3 // } console.log(e); // expected output: 1
The logical operators that we discussed related to expressions only evaluate its operands and then returns a boolean. On the other hand, logical assignment operators evaluate their left side operand, and based on the boolean’s value, assign them a new value based on the right-side operand.
&&=
:a = 4; b = 0; b &&= a; console.log(b); // expected value: 0 // As b = 0 which evaluates to false, b isn't assigned a's value. a &&= b; console.log(a); // expected value: 0 // As a = 4 which evaluates to true, a is assigned b's value.
||=
:The ||=
does the opposite work of &&=
.
a = 4; b = 0; a ||= b; console.log(a); // expected value: 4 // As a = 4 which evaluates to true, a isn't assigned b's value. b ||= a; console.log(b); // expected value: 4 // As b = 0 which evaluates to false, b is assigned a's value.
??=
:If you’ve never heard of it, the ??=
operator does two things: one, it checks if its left-hand operand has a value, and two, assigns a value to any operand that does not have a value.
In this example, the LHS operands are life.time
and life.money
). Since life.money
does not have a value, the logical nullish operator will assign a value to it. Since life.time
does have a value (50
), it will not be affected by ??=
.
const life = { time: 50, money: null }; console.log((a.time ??= 10)); // expected output: 50 console.log((a.money ??= 25)); // expected output: 25
In the following section, we will take a look at shorthand binary operators for the arithmetic and bitwise operations that we’ve studied in the above sections.
+=
:(a = 4), (b = 2); a += b; console.log(a); // expected output: 6
-=
:(a = 4), (b = 2); a -= b; console.log(a); // expected output: 2
/=
:(a = 4), (b = 2); a /= b; console.log(a); // expected output: 2
*=
:(a = 4), (b = 2); a *= b; console.log(a); // expected output: 8
**=
:(a = 4), (b = 2); a **= b; console.log(a); // expected output: 16
%=
:(a = 4), (b = 2); a %= b; console.log(a); // expected output: 0
&=
:(a = 4), (b = 2); a &= b; console.log(a); // expected output: 0
^=
:(a = 4), (b = 2); a ^= b; console.log(a); // expected output: 6
|=
:(a = 4), (b = 2); a |= b; console.log(a); // expected output: 6
<<=
:(a = 4), (b = 2); a <<= b; console.log(a); // expected output: 16
>>=
:(a = 4), (b = 2); a >>= b; console.log(a); // expected output: 1
>>>=
:(a = 4), (b = 2); a >>>= b; console.log(a); // expected output: 1
condition ? ifTrue : ifFalse
):Finally, we would be remiss to not discuss the only operator that takes three operands; ladies and gentleman: the conditional operator.
a = 6; console.log(a > 5 ? "bigger" : "smaller"); // expected output: "bigger" console.log(a < 5 ? "bigger" : "smaller"); // expected output: "smaller"
As you will notice, the conditional (a.k.a. ternary) operator checks if the condition is true or false (hence the question mark ?
), then execute one of the two expressions (placed between the colon :
) based on whether the condition is true or false.
Congratulations! You have made it through this comprehensive guide to expressions and operators in JavaScript. For future reference, it may be useful to bookmark the summary table below, which recaps all of the material we have covered in this article (just open the image in a new tab and zoom in!).
Thank you for reading, and please check out my work at NadaRifki.com. I am also always happy to read your comments or your messages on Twitter (@RifkiNada). 😜
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.