This post covers similarities and differences between the abstract and strict equality operators in JavaScript, and how to use them based on the type of comparison you want to perform.
The abstract and strict comparison operators can be used to check the equality of two operands in JavaScript. Both operators will check the equality of two values and return the boolean value (true or false) based on whether the values are equal or not. This post will discuss the differences between the two operators and how to use them effectively depending on the type of comparison you want to perform.
Before we begin, let’s look at the different value data types in JavaScript and how they can be coerced to boolean values (true or false).
Coercion
Changing values from one type to another (string to number) is referred to as coercion or type conversion in JavaScript. JavaScript has eight basic value data types, which are identified as primitives or non-primitives.
Primitive Types
- Undefined
- Null
- Boolean
- String
- Symbol
- Number
- BigInt
Non-Primitive Types
- Object
- Function
- Array
You can be specific about your intent to convert a value from one type to another (explicit coercion) by using built-in functions such as Number()
, Boolean()
, etc., or you can allow JavaScript to handle the conversion for you
(implicit coercion).
With this in mind, the JavaScript specification defines a list of values that, when coerced to boolean, will return false, and they include the following:
Undefined
null
false
NaN
0, -0, +0
"", " "
0n
The values listed above are known as falsy values because they evaluate to false when encountered in boolean contexts.
Let’s take an example.
let foo = ""
if (!foo) {
// this code is executed because the empty string is a falsy value
console.log("Hello, I am a falsy value")
}
We are using a conditional statement (the if
clause) to evaluate the truthiness of the value assigned to the foo
variable. What happens is JavaScript implicitly coerces the value assigned to foo
to a boolean, and
seeing that the empty string ""
is on the list of falsy values, it evaluates to false.
The following will be logged to the console:
"Hello, I am a falsy value"
If we change the value assigned to the foo
variable to any of the values listed on the falsy list, they will all be coerced to the boolean value false.
Values that are not on the list of falsy values are known as truthy values. Contrary to falsy values, truthy values will be evaluated as true
when encountered in boolean contexts. Considering the list of falsy values is a very short one,
here are some examples of truthy values.
{}
[]
true
10
"Hello"
Comparing values in JavaScript with any of the equality operators generally results in a boolean value indicating the result of the comparison. It returns true if the two values are equal and false if they are not equal. Although the strict and loose operators are both used in JavaScript for equality comparison, the way they perform an equality check is quite different. Let us see the similarities and differences between them.
Strict Equality Operator
The strict equality operator ===
, also known as triple equals, compares both the value and the type of its operands. It is a binary operator, and it uses the algorithm defined in the JavaScript specification for the IsStrictlyEqual
abstract operation to compare values to check if they are equal.
It first determines whether or not the values are of the same type; if they are not, it returns false. If both values are of the same type, it checks if the values are the same; it returns false if the values do not match—except for a few cases, which we will cover in a bit.
Let’s look at some examples.
// Same type, same value
1 === 1 // true
// different types, same value
"2" === 2 // false
In this first example above, we compare the numbers 1 and 1. True
is returned because they have the same value type (both numbers) and equal values. In the second example, we are comparing a numeric string literal to a number. Although they
have the same value (the number 2), false
is returned because they are of different types.
The strict equality operator checks if both operands are of the same type, and then it goes ahead to compare their values, but it does not perform type conversion. If they are not of the same type, it doesn’t matter what their values are. It immediately returns false, and the values are considered unequal. So the strict equality operator only returns true if both operands are equal and of the same type.
Let’s take some more examples
let a ="Ife" + "oma"
let b = "Ifeoma"
a === b // --> true
"Ify" === "ify" // false
The strict equality operator considers strings in JavaScript “equal” when the characters in the string are the same and are of the same length. In the first example above, we compare two string values with the same number of characters and
length. In the second example, we are performing a case insensitive comparison. The first string Ify
is capitalized, and the second string ify
isn’t, so we get false.
According to JavaScript’s specification:
- If Type(x) is String, then:
a. If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true; otherwise, return false.
The algorithm used by the strict equality operator treats a few cases differently.
// NaN is never equal to itself
NaN === NaN // false
// zero signed zeros are equal to each other
-0 === +0 // true
0 === -0 // true
"-0" === "+0" // false
As seen in the example above, NaN
is not equal to itself or any other value in JavaScript. So if both values are NaN
, it returns false
. Zero and signed zeros are considered equal even though they are not the same
value. The strict equality operator only considers signed zeros equal if they are both numbers.
If you need to differentiate between signed zeros, you can use
Object.is()
.
To determine if a value is NaN, useNumber.isNaN()
.
Abstract Equality Operator
The abstract equality operator ==
is also known as the double equals or the loose equality operator. Using this comparison operator allows JavaScript to do type coercion when the types are different. When comparing two values with this operator,
if both operands are not the same type, JavaScript attempts to resolve the data types by implicitly converting them to the same type before comparing them. The abstract equality operator uses the algorithm defined in the JavaScript specification for
the IsLooselyEqual
abstract operation to compare values.
According to the algorithm defined for the IsLooselyEqual
abstract operation, the first thing that happens when you use the abstract equality operator for comparison is that it checks if both values are of the same type.
If they are of the same type, it performs the strict equality comparison.
Under
IsLooselyEqual
:
- If Type(x) is the same as Type(y), then:
a. Return IsStrictlyEqual(x, y).
Let’s take a look at a few examples.
let a ="Ife" + "oma"
let b = "Ifeoma"
a == b // true
15 == 15 // true
As seen in the examples above, both operands are of the same type and have the same value, so true is returned. However, when the types match, but the values are not the same, false
is returned. So we can say that if both values are of the
same type, the abstract equality operator and the strict equality operator do the same thing. It doesn’t do any coercion when the types match, and it simply returns false
if the values are not the same.
Let’s take some examples to see what the abstract equality operator does when it encounters values whose types don’t match.
// double equals allow coercive equality between null and undefined
let a
let b = null
let c = undefined
a == b // true
b == c // true
a == c // true
a === b // false
null == 0 // false
undefined == 0 // false
// ToNumber abstract operation is called to convert the string to number
12 == "12" // true
12 == "13" // false
12 == "twelve" // false
As we can see from the example above, null
and undefined
compared with the abstract equality operator are coercively equal to each other and no other values in the language.
According to the algorithm defined for the IsLooselyEqual
abstract operation, if a value of type string
is compared to a value of type number
, the ToNumber
abstract operation is called on the string
value to convert it to a number
first before performing the comparison.
In the example above, where we’re comparing the number 12 to the string “12”, JavaScript performs the following:
12 == "12"
12 == ToNumber("12") // The string gets converted to a number
12 == 12
12 === 12 // true
After converting the string to a number, both operands are now of the same type. Since they are of the same type, JavaScript performs the strict equality comparison, and true
is returned because their value is also the same.
Remember, the JavaScript specification says that it performs the strict equality comparison when comparing with the abstract equality operator and the types match. Also, after the abstract equality operator performs type coercion and the types match,
it performs strict equality comparison on both values. If the values are the same, true
is returned; otherwise, false
is returned.
In JavaScript, the boolean values true
and false
are loosely equal to numbers 1
and 0
when compared with the abstract equality operator.
According to the algorithm for the
IsLooselyEqual
abstract operation:
- If Type(x) is Number and Type(y) is String, return IsLooselyEqual(x, ! ToNumber(y)).
- If Type(x) is String and Type(y) is Number, return IsLooselyEqual(! ToNumber(x), y).
Let’s take some examples to see how booleans behave when compared with the abstract equality operator.
// ToNumber gets called to convert the boolean to number
true == 1 // true
ToNumber(true) == 1
1 == 1
1 === 1 // true
0 == false // true
ToNumber(false) == 0
0 == 0
0 === 0 // true
The example above shows that the ToNumber()
abstract operation gets called to coerce the boolean values to numbers first before comparing them. Because the types are now equal after coercion, it performs the strict equality comparison.
Comparing Objects With Abstract and Strict Equality Operators
When comparing two objects of the same type with either the abstract or strict equality operator, they are equal if both operands reference the same object.
Let’s take some examples:
let a = ["Hello", "world"]
let b = a
let c = ["Hello", "world"]
let d = { a: 1, b: 2 }
let e = d
let f = { a: 1, b: 2 }
a == b // true
a === b // true
a == c // false
b == c // false
d == e // true
d === e // true
d == f // false
e == f // false
As seen in the example above, the variables a
and b
both point to the same array object, so when we compared them with the abstract or strict equality operator, true was returned. The variables a
and c
are of the same type and even have the same value, but false
was returned because they point to two objects in memory.
The variables d
and e
also point to the same object, so true
is returned when compared. When comparing objects, the types of both operands must match, and they must reference the same object to be considered
equal.
On the other hand, when you compare a primitive value with an object using the abstract equality operator, the object is first converted to its primitive equivalent before the comparison is performed.
The algorithm for the
IsLooselyEqual
abstract operation states that:
- If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return IsLooselyEqual(x, ? ToPrimitive(y)).
var a = new String("Hello")
var b = "Hello"
a == b // true
a === b // false
In the example above, we created a string object by calling the string constructor with the new keyword and assigned it to the variable a
. We’re comparing this string object to a string literal which we assigned to the variable
b
. When comparing them with the abstract equality operator, the ToPrimitive()
abstract operation gets called to convert the object to a string before the comparison is performed, and that’s why true is returned.
The strict equality operator sees that the comparison is between two values of different types, and it immediately returns false
.
Conclusion
In JavaScript, the abstract and strict equality operators are equally useful for determining equality. We can conclude that the strict equality operator requires both the types and the values to match to be considered equal, but the abstract equality operator allows coercion when the operands are not of the same type.
We also saw that when the abstract equality operator performs type coercion, it prefers to convert to a number before proceeding with the comparison. Finally, the abstract equality operator prefers comparison between primitives. When a primitive is compared to a non-primitive, the non-primitive is first converted to its primitive equivalent.
Many books and blogs recommend using the strict equality operator as a better option; however, it depends on what you want (do you want to allow coercion or not). When writing JavaScript, you’ll most likely use coercion without thinking about it. So it doesn’t matter which one you choose—knowing the difference and understanding how they work will only make you a better developer.
Next up, you may want to explore the different data producers in JavaScript—Functions, Promises, Iterables and Observables.