DEV Community

Tony Chase
Tony Chase

Posted on

Learning JS in 30 Days - Day 7

πŸ“š Today's Learning Goals

  • Understand JavaScript scope concepts
  • Master variable hoisting mechanism
  • Learn the differences between let, const, and var
  • Understand the basics of closures

🌍 Scope

Scope determines the accessibility of variables and functions.

Global Scope

// Global variables
var globalVar = "I am a global variable";
let globalLet = "I am also a global variable";
const globalConst = "I am still a global variable";

function testGlobalScope() {
  console.log(globalVar); // "I am a global variable"
  console.log(globalLet); // "I am also a global variable"
  console.log(globalConst); // "I am still a global variable"
}

testGlobalScope();
console.log(globalVar); // "I am a global variable" (accessible outside function)
Enter fullscreen mode Exit fullscreen mode

Local Scope

Function Scope

function testFunctionScope() {
  var localVar = "I am a local variable";
  let localLet = "I am also a local variable";
  const localConst = "I am still a local variable";

  console.log(localVar); // "I am a local variable"
  console.log(localLet); // "I am also a local variable"
  console.log(localConst); // "I am still a local variable"
}

testFunctionScope();
// console.log(localVar); // ReferenceError: localVar is not defined
// console.log(localLet); // ReferenceError: localLet is not defined
// console.log(localConst); // ReferenceError: localConst is not defined
Enter fullscreen mode Exit fullscreen mode

Block Scope

// var has no block scope
if (true) {
  var blockVar = "var variable";
}
console.log(blockVar); // "var variable" (accessible)

// let and const have block scope
if (true) {
  let blockLet = "let variable";
  const blockConst = "const variable";
}
// console.log(blockLet); // ReferenceError: blockLet is not defined
// console.log(blockConst); // ReferenceError: blockConst is not defined

// Block scope in loops
for (let i = 0; i < 3; i++) {
  // i is only valid within the loop block
}
// console.log(i); // ReferenceError: i is not defined

for (var j = 0; j < 3; j++) {
  // j is accessible outside the loop
}
console.log(j); // 3
Enter fullscreen mode Exit fullscreen mode

⬆️ Variable Hoisting

Variable hoisting is a JavaScript feature where variable and function declarations are "hoisted" to the top of their scope.

var Hoisting

// Before code execution, the JavaScript engine processes it like this:
console.log(hoistedVar); // undefined (no error)
var hoistedVar = "I was hoisted";

// Equivalent to:
var hoistedVar; // Declaration is hoisted to the top
console.log(hoistedVar); // undefined
hoistedVar = "I was hoisted"; // Assignment stays in place
Enter fullscreen mode Exit fullscreen mode

Temporal Dead Zone for let and const

// let and const are not hoisted, they have a temporal dead zone
console.log(hoistedLet); // ReferenceError: Cannot access 'hoistedLet' before initialization
let hoistedLet = "I am not hoisted";

console.log(hoistedConst); // ReferenceError: Cannot access 'hoistedConst' before initialization
const hoistedConst = "I am also not hoisted";
Enter fullscreen mode Exit fullscreen mode

Function Hoisting

// Function declarations are fully hoisted
console.log(hoistedFunction()); // "I was fully hoisted"

function hoistedFunction() {
  return "I was fully hoisted";
}

// Function expressions are not hoisted
console.log(notHoistedFunction()); // TypeError: notHoistedFunction is not a function

var notHoistedFunction = function () {
  return "I was not hoisted";
};
Enter fullscreen mode Exit fullscreen mode

πŸ”„ var vs let vs const

Scope Comparison

// var - function scope
function testVar() {
  if (true) {
    var varVariable = "var variable";
  }
  console.log(varVariable); // "var variable" (accessible)
}

// let - block scope
function testLet() {
  if (true) {
    let letVariable = "let variable";
  }
  // console.log(letVariable); // ReferenceError: letVariable is not defined
}

// const - block scope
function testConst() {
  if (true) {
    const constVariable = "const variable";
  }
  // console.log(constVariable); // ReferenceError: constVariable is not defined
}
Enter fullscreen mode Exit fullscreen mode

Duplicate Declaration Comparison

// var allows duplicate declarations
var duplicateVar = "First time";
var duplicateVar = "Second time"; // No error
console.log(duplicateVar); // "Second time"

// let does not allow duplicate declarations
let duplicateLet = "First time";
// let duplicateLet = "Second time"; // SyntaxError: Identifier 'duplicateLet' has already been declared

// const does not allow duplicate declarations
const duplicateConst = "First time";
// const duplicateConst = "Second time"; // SyntaxError: Identifier 'duplicateConst' has already been declared
Enter fullscreen mode Exit fullscreen mode

Reassignment Comparison

// var can be reassigned
var reassignableVar = "Original value";
reassignableVar = "New value";
console.log(reassignableVar); // "New value"

// let can be reassigned
let reassignableLet = "Original value";
reassignableLet = "New value";
console.log(reassignableLet); // "New value"

// const cannot be reassigned
const reassignableConst = "Original value";
// reassignableConst = "New value"; // TypeError: Assignment to constant variable
Enter fullscreen mode Exit fullscreen mode

Real-world Application Example

// Classic problem: var in loops
console.log("=== var problem in loops ===");
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log("var i:", i); // Output: 3, 3, 3
  }, 100);
}

// Solution 1: Use let
console.log("=== let solution ===");
for (let j = 0; j < 3; j++) {
  setTimeout(() => {
    console.log("let j:", j); // Output: 0, 1, 2
  }, 200);
}

// Solution 2: Use closure
console.log("=== closure solution ===");
for (var k = 0; k < 3; k++) {
  (function (index) {
    setTimeout(() => {
      console.log("closure k:", index); // Output: 0, 1, 2
    }, 300);
  })(k);
}
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Closure Basics

Closures refer to a function's ability to access variables from its outer scope.

Basic Closure Example

function outerFunction(x) {
  // Outer function variable
  let outerVariable = x;

  // Inner function
  function innerFunction(y) {
    // Inner function can access outer function variables
    return outerVariable + y;
  }

  return innerFunction;
}

// Create closure
let closure = outerFunction(10);
console.log(closure(5)); // 15

// Another closure
let anotherClosure = outerFunction(20);
console.log(anotherClosure(5)); // 25
Enter fullscreen mode Exit fullscreen mode

Real-world Closure Applications

// 1. Data privacy
function createCounter() {
  let count = 0; // Private variable

  return {
    increment: function () {
      count++;
      return count;
    },
    decrement: function () {
      count--;
      return count;
    },
    getCount: function () {
      return count;
    },
  };
}

let counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// console.log(counter.count); // undefined (cannot directly access private variable)

// 2. Function factory
function createMultiplier(factor) {
  return function (number) {
    return number * factor;
  };
}

let double = createMultiplier(2);
let triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15
Enter fullscreen mode Exit fullscreen mode

Closure Considerations

// Closures can cause memory leaks
function createFunctions() {
  let functions = [];

  for (var i = 0; i < 3; i++) {
    functions.push(function () {
      return i; // All functions reference the same i
    });
  }

  return functions;
}

let funcs = createFunctions();
funcs.forEach((func) => {
  console.log(func()); // Output: 3, 3, 3
});

// Correct closure usage
function createCorrectFunctions() {
  let functions = [];

  for (let i = 0; i < 3; i++) {
    functions.push(function () {
      return i; // Each function has its own i
    });
  }

  return functions;
}

let correctFuncs = createCorrectFunctions();
correctFuncs.forEach((func) => {
  console.log(func()); // Output: 0, 1, 2
});
Enter fullscreen mode Exit fullscreen mode

🎯 Practice Exercises

Exercise 1: Scope Testing

function scopeTest() {
  console.log("=== Scope Test ===");

  // Global variables
  var globalVar = "Global var";
  let globalLet = "Global let";
  const globalConst = "Global const";

  function innerFunction() {
    // Local variables
    var localVar = "Local var";
    let localLet = "Local let";
    const localConst = "Local const";

    console.log("Inner function can access:");
    console.log("Global var:", globalVar);
    console.log("Global let:", globalLet);
    console.log("Global const:", globalConst);
    console.log("Local var:", localVar);
    console.log("Local let:", localLet);
    console.log("Local const:", localConst);
  }

  innerFunction();

  console.log("Outer function can access:");
  console.log("Global var:", globalVar);
  console.log("Global let:", globalLet);
  console.log("Global const:", globalConst);
  // console.log("Local var:", localVar); // ReferenceError
}

scopeTest();
Enter fullscreen mode Exit fullscreen mode

Exercise 2: Hoisting Test

function hoistingTest() {
  console.log("=== Hoisting Test ===");

  // var hoisting
  console.log("var variable:", hoistedVar); // undefined
  var hoistedVar = "var value";

  // Function hoisting
  console.log("Function call:", hoistedFunc()); // "Function was hoisted"

  function hoistedFunc() {
    return "Function was hoisted";
  }

  // let temporal dead zone
  try {
    console.log("let variable:", hoistedLet);
  } catch (error) {
    console.log("let error:", error.message);
  }
  let hoistedLet = "let value";

  // const temporal dead zone
  try {
    console.log("const variable:", hoistedConst);
  } catch (error) {
    console.log("const error:", error.message);
  }
  const hoistedConst = "const value";
}

hoistingTest();
Enter fullscreen mode Exit fullscreen mode

Exercise 3: Closure Application - Cached Function

function createCachedFunction(fn) {
  let cache = {}; // Private cache

  return function (...args) {
    let key = JSON.stringify(args);

    if (cache[key]) {
      console.log("Getting result from cache");
      return cache[key];
    }

    console.log("Computing new result");
    let result = fn(...args);
    cache[key] = result;
    return result;
  };
}

// Create cached version of fibonacci function
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

let cachedFibonacci = createCachedFunction(fibonacci);

// Test caching functionality
console.log("=== Cached Function Test ===");
console.log("fibonacci(10):", cachedFibonacci(10));
console.log("fibonacci(10):", cachedFibonacci(10)); // Get from cache
console.log("fibonacci(5):", cachedFibonacci(5));
console.log("fibonacci(5):", cachedFibonacci(5)); // Get from cache
Enter fullscreen mode Exit fullscreen mode

πŸ” Today's Key Points Summary

  1. Scope: Global scope, function scope, block scope
  2. Variable Hoisting: var is hoisted, let/const have temporal dead zone
  3. Variable Declarations: Differences and usage scenarios for var, let, const
  4. Closure Basics: Function's ability to access outer scope variables
  5. Best Practices: Recommend using let/const, avoid var problems

πŸ“š Tomorrow's Preview

Tomorrow we'll learn about object basics, including:

  • Object creation and access
  • Object methods and properties
  • Object iteration and manipulation
  • Object literal syntax

πŸ’‘ Learning Tips

  1. Understand scope: Master the characteristics of different scopes
  2. Avoid hoisting pitfalls: Use let/const instead of var
  3. Master closures: Understand closure concepts and applications
  4. Practice: Understand these concepts through actual code

Congratulations on completing Day 7! πŸŽ‰

You've mastered scope and variable hoisting. Tomorrow we'll learn about JavaScript's core data structure - objects!

Top comments (0)