JavaScript Interesting Parts: Comprehensive Guide
JavaScript has unique features and behaviors that can be puzzling, especially for beginners. These “weird parts” often result from its dynamic typing, prototypal inheritance, and loose interpretation of data types. This guide will explore these quirks, explain them, and provide exercises and quiz questions to help you master them.
1. Hoisting
In JavaScript, declarations are moved (“hoisted”) to the top of their scope, but initialization remains in place.
Example: Variable Hoisting
console.log(a); // Output: undefined
var a = 5;
Explanation:
- The variable a is hoisted but not its assignment.
This is equivalent to:
var a;
console.log(a); // undefined
a = 5;
Example: Function Hoisting
greet(); // Output: Hello!
function greet() {
console.log(“Hello!”);
}
Explanation:
- Function declarations are hoisted with their definitions.
Exercise:
What will the following code output?
console.log(b);
let b = 10;
Answer:
- Error: ReferenceError: Cannot access ‘b’ before initialization.
- Variables declared with let or const are hoisted but remain in a “temporal dead zone” until initialization.
2. Coercion
Coercion converts a value from one type to another, often implicitly.
Example: Implicit Coercion
console.log(1 + “2”); // Output: “12”
console.log(1 – “2”); // Output: -1
console.log(“5” * 2); // Output: 10
Explanation:
- 1 + “2”: Number 1 is coerced into a string, resulting in concatenation.
- 1 – “2”: String “2” is coerced into a number for subtraction.
Exercise:
Predict the output:
console.log(true + false); // ?
console.log([] + {}); // ?
console.log({} + []); // ?
Answer:
- true + false: 1 (booleans are coerced to 1 and 0).
- [] + {}: “[object Object]” (empty array coerced to an empty string; object coerced to string).
- {} + []: 0 (interpreted as an empty block {} and array coerced to 0).
3. The this Keyword
The value of this depends on how a function is called.
Example: Global Scope
console.log(this); // In browsers, Output: Window object
Example: Object Method
const obj = {
name: “Alice”,
greet() {
console.log(this.name);
},
};
obj.greet(); // Output: “Alice”
Example: Arrow Functions
const obj = {
name: “Alice”,
greet: () => {
console.log(this.name);
},
};
obj.greet(); // Output: undefined
Explanation:
- Arrow functions do not have their own this and inherit it from the enclosing scope.
Exercise:
What will the following code output?
const obj = {
name: “Alice”,
greet() {
const inner = () => {
console.log(this.name);
};
inner();
},
};
obj.greet();
Answer:
- Output: “Alice” (arrow function inherits this from greet).
4. Prototypal Inheritance
JavaScript uses prototypes for inheritance, where objects inherit properties and methods from their prototype chain.
Example: Prototype Chain
const animal = {
eats: true,
};
const dog = Object.create(animal);
dog.barks = true;
console.log(dog.eats); // Output: true
Explanation:
- dog inherits from animal because of Object.create.
Exercise:
What will the following code output?
function Animal() {}
Animal.prototype.walk = function () {
return “Walking”;
};
const dog = new Animal();
console.log(dog.walk());
Answer:
- Output: “Walking” (dog inherits walk from Animal.prototype).
5. Closures
Closures are functions that retain access to their outer scope, even after the outer function has executed.
Example: Basic Closure
function outer() {
const outerVar = “I’m outer”;
return function inner() {
console.log(outerVar);
};
}
const closure = outer();
closure(); // Output: “I’m outer”
Exercise:
What will the following code output?
function counter() {
let count = 0;
return function () {
return ++count;
};
}
const increment = counter();
console.log(increment());
console.log(increment());
Answer:
Output:
1
2
6. Equality Comparisons
Example: Loose vs. Strict Equality
console.log(0 == false); // true
console.log(0 === false); // false
Explanation:
- == performs type coercion.
- === checks both value and type.
Exercise:
What will the following code output?
console.log(null == undefined);
console.log(null === undefined);
Answer:
- null == undefined: true (loose equality treats them as equivalent).
- null === undefined: false (strict equality checks type).
Multiple-Choice Questions
Question 1:
What is the output of the following code?
function test() {
console.log(this);
}
test();
- undefined
- null
- Window object
- {} (empty object)
Answer: 3. Window object (in browsers).
Question 2:
What will the following code output?
console.log(typeof null);
- “null”
- “object”
- “undefined”
- “function”
Answer: 2. “object”
Question 3:
Which of the following is true about var declarations?
- Variables declared with var are block-scoped.
- var declarations are not hoisted.
- Variables declared with var are function-scoped.
- var variables are constants.
Answer: 3. Variables declared with var are function-scoped.
Exercises
Exercise 1: Fix this in a Callback
Given the following code, fix the issue with this using an arrow function.
function User(name) {
this.name = name;
}
User.prototype.sayHi = function () {
setTimeout(function () {
console.log(`Hi, I’m ${this.name}`);
}, 1000);
};
const user = new User(“Alice”);
user.sayHi();
Solution:
User.prototype.sayHi = function () {
setTimeout(() => {
console.log(`Hi, I’m ${this.name}`);
}, 1000);
};
Exercise 2: Create a Counter Using Closures
Write a function createCounter that returns a counter function. The counter should increment by 1 each time it is called.
Exercise 3: Check Prototypal Inheritance
Given the following code, confirm if dog inherits from animal.
const animal = { eats: true };
const dog = Object.create(animal);
Solution:
console.log(animal.isPrototypeOf(dog)); // true
Best Practices
- Avoid Implicit Coercion: Use strict equality (===) for predictable comparisons.
- Use let and const: Avoid issues with var hoisting and scoping.
- Understand Scope: Be cautious with closures and this.
- Test Edge Cases: Check unexpected behaviors in type coercion and equality.