JavaScript Interesting Parts: Comprehensive Guide

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:

  1. true + false: 1 (booleans are coerced to 1 and 0).
  2. [] + {}: “[object Object]” (empty array coerced to an empty string; object coerced to string).
  3. {} + []: 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();

  1. undefined
  2. null
  3. Window object
  4. {} (empty object)

Answer: 3. Window object (in browsers).

Question 2:

What will the following code output?

console.log(typeof null);

  1. “null”
  2. “object”
  3. “undefined”
  4. “function”

Answer: 2. “object”

Question 3:

Which of the following is true about var declarations?

  1. Variables declared with var are block-scoped.
  2. var declarations are not hoisted.
  3. Variables declared with var are function-scoped.
  4. 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

  1. Avoid Implicit Coercion: Use strict equality (===) for predictable comparisons.
  2. Use let and const: Avoid issues with var hoisting and scoping.
  3. Understand Scope: Be cautious with closures and this.
  4. Test Edge Cases: Check unexpected behaviors in type coercion and equality.