Introduction
Welcome, fellow readers! Today, we’re diving into the tricky side of Javascript. Before we begin, let’s clear up some things. Javascript is a programming language used to create interactive and dynamic web content. Tricky outputs refer to nuances in the language that may catch you off guard if you’re not familiar with them. It’s crucial to understand these outputs because they can affect the functionality and reliability of your code.
In this blog, we will explore some of the most common JavaScript tricky outputs, along with explanations and examples to help you understand and navigate through these challenges.
1. NaN — Not a Number
One of the most notorious tricky outputs in JavaScript is NaN, which stands for “Not a Number.” It is often encountered when performing mathematical operations that result in an undefined or incorrect value.
a. Invalid Mathematical Operations
When you perform mathematical operations that involve non-numeric values or undefined variables, you get NaN. For example:
console.log(0 / 0); // Output: NaN
console.log("Hello" - 10); // Output: NaN
console.log("Hello" * 10); // Output: NaN
b. parseInt and parseFloat:
The parseInt and parseFloat functions can return NaN
if the string they are parsing doesn't represent a valid number:
let num = parseInt("abc"); // NaN
c. Math Functions:
Certain math functions may return NaN
for invalid inputs:
let result = Math.sqrt(-1); // NaN
d. Comparing NaN:
NaN is not equal to any value, including itself. So, comparing NaN with anything, even another NaN
, results in false:
let nan1 = NaN;
let nan2 = NaN;
console.log(nan1 === nan2); // false
e. Object Conversion:
Attempting to convert an object that doesn’t have a meaningful numeric representation to a number can result in NaN
:
let obj = { key: "value" };
let num = Number(obj); // NaN
f. Undefined Variables:
Using a variable that is not defined results in NaN
:
let undefinedVar;
let result = undefinedVar * 10; // NaN
g. NaN as a Property Value:
You can set a property of an object to NaN explicitly:
let obj = { prop: NaN };
Remember that NaN
is used to indicate that something went wrong with a numeric operation, so it’s essential to handle it properly in your code, especially when it might occur unexpectedly. You can use the isNaN()
function or the Number.isNaN()
method to check for NaN
values and handle them accordingly.
console.log(isNaN(NaN)); // Output: true
console.log(isNaN(10)); // Output: false
2. Type Coercion
Type coercion in JavaScript is the process of converting one data type into another implicitly or explicitly. It can sometimes lead to unexpected behavior if you’re not aware of how JavaScript handles type conversions. Let’s explore some examples of type coercion and discuss potential solutions.
Example 1:
Implicit Type Coercion
let num = 5;
let str = "10";
let result = num + str; // JavaScript implicitly converts num to a string and performs string concatenation
console.log(result); // "510"
In this example, JavaScript implicitly converts the num variable to a string and then concatenates it with the string in the str variable. To avoid implicit coercion, you can use explicit type conversion (casting) to ensure you get the desired behavior:
Solution 1:
Explicit Type Conversion
let num = 5;
let str = "10";
let result = num + Number(str); // Explicitly convert str to a number
console.log(result); // 15
Example 2:
Loose Equality Operator (==)
let num = 5;
let str = "5";
if (num == str) {
console.log("Equal");
} else {
console.log("Not Equal");
}
// Output: Equal
In this example, the loose equality operator (==) performs type coercion and considers the values as equal even though one is a number and the other is a string.
Solution 2:
Strict Equality Operator (===)
let num = 5;
let str = "5";
if (num === str) {
console.log("Equal");
} else {
console.log("Not Equal");
}
// Output: Not Equal
Using the strict equality operator (===) ensures that both the value and the type must be the same for the condition to be true. In this case, it would correctly identify them as not equal.
Example 3:
Truthy and Falsy Values
let value = "Hello";
if (value) {
console.log("Truthy");
} else {
console.log("Falsy");
}
// Output: Truthy
JavaScript treats certain values as “truthy” (values that evaluate to true) and others as "falsy" (values that evaluate to false). This can lead to unexpected behavior when working with conditional statements.
Solution 3:
Use Explicit Comparisons
let value = "Hello";
if (value !== undefined && value !== null && value !== false && value !== 0 && value !== "") {
console.log("Truthy");
}
3. Hoisting
Hoisting in JavaScript can be tricky to understand, especially when it comes to variable and function declarations. Let’s walk through an example step by step:
Consider the following JavaScript code:
console.log(x); // Outout: undefined , Step 1: This line is executed, but 'x' is not defined yet.
var x = 5; // Step 2: Variable 'x' is declared and initialized to 5.
console.log(x); // Output: 5, Step 3: This line now logs the value of 'x', which is 5.
Now, let’s break down what happens step by step:
Step 1:
The code begins executing, and it encounters the console.log(x) statement.
At this point, x is not defined (no value assigned), but it doesn't result in an error. Instead, JavaScript recognizes x as a variable declaration due to hoisting.
The variable x is hoisted to the top of its containing scope (in this case, the global scope), but it's not yet assigned a value. So, console.log(x) logs undefined.
Step 2:
Now, we have
var x = 5;
. Here, x is declared and initialized to 5.The assignment
x = 5
happens at this point.At the same time, JavaScript has already hoisted the variable declaration var x to the top of the scope, so it knows about the variable.
Step 3:
Finally, we encounter
console.log(x)
again.Now, x has a value of 5 because it was assigned that value in Step 2.
The line logs 5 to the console.
4. The ‘this’ Keyword
The this keyword in JavaScript can be tricky to work with because its value depends on how a function is called. It doesn't always refer to the object or context you might expect. Here are some tricky situations involving this in JavaScript along with their solutions:
Example 1:
Global Context
console.log(this); // refers to the global object (e.g., window in a browser)
Solution 1:
There’s no direct way to change the behavior of this in the global context. It will always refer to the global object. You can avoid relying on it in global scope or use strict mode to prevent accidental global variable creation.
Examples 2:
Regular Functions vs. Arrow Functions
const person = {
name: 'John',
regularFunc: function () {
console.log(`Hello, my name is ${this.name}`); // refers to the 'person' object
},
arrowFunc: () => {
console.log(`Hello, my name is ${this.name}`); // refers to the enclosing scope (likely the global object)
},
};
person.regularFun(); // Output: Hello, my name is John
person.arrowFun(); // Output: Hello, my name is undefined
In the above example, the regularFunc method is a regular function defined within the person object. When person.regularFunc() is called, this inside the regularFunc function refers to the person object. Therefore, this.name accesses the name property of the person object, resulting in the output “Hello, my name is John”.
The arrowFunmethod is an arrow function defined within the person object. When person.arrowFun() is called, this inside the arrow function does not refer to the person object. Instead, it inherits the this value from the surrounding lexical scope, which is the global object. As a result, this.name does not exist in the global object, leading to the output “Hello, my name is undefined”.
Solution 2:
Use regular functions when you need access to the object’s context (this). Arrow functions do not have their own this, and they inherit it from the enclosing scope.
Example 3:
Event Handlers
const button = document.querySelector('button');
button.addEventListener('click', function () {
console.log(this); // refers to the button element
});
Solution 3:
In event handlers, this often refers to the DOM element that triggered the event. If you need to access the outer context, store it in a variable before the event handler.
const button = document.querySelector('button');
const self = this; // Store the context
button.addEventListener('click', function () {
console.log(self); // refers to the outer context
});
Example 4:
Constructor Functions
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
console.log(person.name); // Alice
Constructor functions create new objects and set this to refer to the newly created object. This behavior is not an issue unless you forget to use the new keyword.
Example 5:
Method Chaining
const calculator = {
value: 0,
add(num) {
this.value += num;
return this;
},
subtract(num) {
this.value -= num;
return this;
},
};
const result = calculator.add(5).subtract(3).value; // What's the result?
// Output: 2
This is a common pattern for method chaining. Each method returns this, allowing you to chain multiple method calls together.
5. Closures
Closures are a powerful concept in JavaScript but can also lead to unexpected outputs if not properly understood. A closure is created every time a function is created, at function creation time.
Consider the following example:
function outer() {
var x = 10;
function inner() {
console.log(x);
}
return inner;
}
var newFunction = outer(); // Outer finished execution and returned inner function
newFunction(); // Output: 10
In the above code, inner() has access to the x variable, even after outer() has finished executing.
What we're actually doing is invoking the inner function, which was returned by the outer function. And even though outer has long since completed its execution, inner can still access the variable x (thanks to the closure), and it prints out 10. Understanding closures is crucial to avoid unexpected behaviors when dealing with nested functions.
Conclusion
So there you have it, tricky outputs in Javascript. We’ve covered NaN — Not a Number, type coercion, hoisting, this keyword, and closures. It’s a lot to take in, we know. But don’t worry, understanding these concepts is crucial for any Javascript developer looking to write clean and efficient code.
It’s easy to fall into common misconceptions or make mistakes with these tricky outputs, but with a little bit of practice, you’ll be able to utilize them to your advantage. So remember, always test your code thoroughly and be mindful of how these outputs can affect your code.
🔗 Connect with me on:
A Big Thank You! 🌟
- Readers: Grateful for every click and read.
- Engagers: Claps, comments, shares — you’re the best!
- Followers: Honored by your continuous support.
- Silent Admirers: Your presence doesn’t go unnoticed.
- Constructive Critics: Your insights help us improve. 💌 Stay connected and thanks for being part of our journey.
Top comments (2)
Unfortunately, this isn't really correct. A closure is created EVERY time a function is created, regardless of whether it was inside another function. You also imply that a closure is a function - which is also incorrect.
Misconceptions About Closures
Jon Randy 🎖️ ・ Sep 27
Thanks @jonrandy ... I will update.