Understanding TypeScript Equality
Equality is a fundamental concept in programming, and TypeScript, as a statically typed superset of JavaScript, offers its own nuances when it comes to determining if two values are equal. In this blog post, we'll explore the different types of equality in TypeScript, how they work, common practices, and best practices to help you write more reliable and bug-free code.
Table of Contents#
- Fundamental Concepts of TypeScript Equality
- Structural Equality
- Referential Equality
- Abstract Equality
- Usage Methods
- Using
==and=== - Using
Object.is()
- Using
- Common Practices
- Comparing Primitives
- Comparing Objects
- Comparing Arrays
- Best Practices
- When to Use
==vs=== - Using
Object.is()Wisely
- When to Use
- Conclusion
- References
Fundamental Concepts of TypeScript Equality#
Structural Equality#
Structural equality means that two objects are considered equal if they have the same shape (i.e., the same properties with the same values). In TypeScript, by default, it doesn't have a built-in operator for direct structural equality. For example, two objects with the same properties and values should be considered equal in a structural sense.
const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
// Although obj1 and obj2 have the same structure, they are not considered equal by default operatorsReferential Equality#
Referential equality checks if two variables refer to the exact same object in memory. In TypeScript, the === (strict equality) operator is used for referential equality when dealing with objects.
const objA = { key: 'value' };
const objB = objA;
const objC = { key: 'value' };
console.log(objA === objB); // true, because they refer to the same object
console.log(objA === objC); // false, different objects in memoryAbstract Equality#
Abstract equality is determined by the == operator. It performs type coercion before comparing values. This can lead to some unexpected results.
console.log(5 == '5'); // true, because '5' is coerced to a numberUsage Methods#
Using == and ===#
==(Abstract Equality): This operator first tries to convert the operands to the same type and then compares them.
console.log(0 == false); // true, because false is coerced to 0===(Strict Equality): This operator checks both the value and the type of the operands.
console.log(0 === false); // false, different typesUsing Object.is()#
The Object.is() method determines whether two values are the same value. It behaves similarly to ===, but with some differences in handling special values like NaN and -0 and +0.
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false
console.log(Object.is(-0, +0)); // false
console.log(-0 === +0); // trueCommon Practices#
Comparing Primitives#
When comparing primitive values (such as numbers, strings, booleans), it's generally recommended to use === to avoid unexpected type coercion.
const num1 = 10;
const num2 = '10';
console.log(num1 === num2); // falseComparing Objects#
For objects, === is used to check if they refer to the same object in memory. If you need to check for structural equality, you can write a custom function.
function isStructurallyEqual(obj1: any, obj2: any): boolean {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length!== keys2.length) {
return false;
}
for (const key of keys1) {
if (obj1[key]!== obj2[key]) {
return false;
}
}
return true;
}
const person1 = { name: 'Alice', age: 25 };
const person2 = { name: 'Alice', age: 25 };
console.log(isStructurallyEqual(person1, person2)); // trueComparing Arrays#
Similar to objects, === checks if two arrays refer to the same array in memory. To compare array contents, you can use a loop or a library function.
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
const arr3 = arr1;
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // true
function areArraysEqual(arrA: any[], arrB: any[]): boolean {
if (arrA.length!== arrB.length) {
return false;
}
for (let i = 0; i < arrA.length; i++) {
if (arrA[i]!== arrB[i]) {
return false;
}
}
return true;
}
console.log(areArraysEqual(arr1, arr2)); // trueBest Practices#
When to Use == vs ===#
- Use
===: In most cases, especially when comparing primitive values. It provides a more predictable and safer comparison by avoiding type coercion. - Use
==: There are rare cases where type coercion can be useful, such as when comparing a value tonullorundefinedin a single check.
const value = null;
if (value == null) {
// This condition is true for both null and undefined
}Using Object.is() Wisely#
Use Object.is() when you specifically need to handle cases where NaN or signed zeros need to be compared accurately.
const result = calculateSomething(); // might return NaN
if (Object.is(result, NaN)) {
// Handle the NaN case
}Conclusion#
Understanding the different types of equality in TypeScript is crucial for writing reliable and bug-free code. The choice between ==, ===, and Object.is() depends on the specific requirements of your comparison. For most primitive value comparisons, === is the recommended choice. When dealing with objects and arrays, be aware of the difference between referential and structural equality. By following these best practices, you can ensure that your equality checks are accurate and predictable.