We use cookies to enhance your experience on the site
CodeWorlds

Variable Scope

In Jurassic Park, different security zones have different access levels - some information is available park-wide, some only in a specific zone, some only in a single secure enclosure. JavaScript has a similar concept: scope determines where a variable is accessible.

Global Scope

Variables declared outside any function or block are in global scope - accessible from anywhere:

1// Global scope - accessible everywhere
2const parkName = 'Jurassic Park';
3let totalDinosaurs = 150;
4
5function checkPark() {
6  console.log(parkName);        // "Jurassic Park" - accessible!
7  console.log(totalDinosaurs);  // 150 - accessible!
8}
9
10checkPark();
11console.log(parkName); // also accessible here

Function Scope

Variables declared inside a function are available only within that function:

1function analyzeDanger() {
2  const dangerLevel = 'HIGH'; // only inside this function
3  let escapedCount = 3;       // only inside this function
4
5  console.log(dangerLevel); // "HIGH" - accessible
6  console.log(escapedCount); // 3 - accessible
7}
8
9analyzeDanger();
10// console.log(dangerLevel); // ReferenceError: dangerLevel is not defined
11// console.log(escapedCount); // ReferenceError: escapedCount is not defined

Block Scope

let
and
const
are block-scoped - accessible only within the block (inside
{}
) where they were declared:

1if (true) {
2  const blockVar = 'I exist only in this block';
3  let anotherBlock = 'me too';
4  var oldStyle = 'I escape from the block!'; // var is NOT block-scoped!
5}
6
7// console.log(blockVar);    // ReferenceError!
8// console.log(anotherBlock); // ReferenceError!
9console.log(oldStyle); // "I escape from the block!" - var ignores blocks

var vs let vs const - Scope Differences

1// var - function scope (ignores blocks)
2function varExample() {
3  for (var i = 0; i < 3; i++) {
4    var innerVar = i; // function-scoped!
5  }
6  console.log(i);        // 3 - accessible! (leaked from loop)
7  console.log(innerVar); // 2 - accessible! (leaked from loop)
8}
9
10// let/const - block scope
11function letExample() {
12  for (let j = 0; j < 3; j++) {
13    let innerLet = j; // block-scoped!
14  }
15  // console.log(j);       // ReferenceError!
16  // console.log(innerLet); // ReferenceError!
17}

Lexical Scope (Static Scope)

Functions "remember" the scope where they were defined - not where they're called. This is called lexical scope:

1const zone = 'Global Zone';
2
3function outerZone() {
4  const zone = 'Outer Zone';
5
6  function innerZone() {
7    const zone = 'Inner Zone';
8    console.log(zone); // "Inner Zone" - closest scope first
9  }
10
11  function anotherInner() {
12    // No local `zone` - goes up the scope chain
13    console.log(zone); // "Outer Zone" - from outerZone
14  }
15
16  innerZone();
17  anotherInner();
18}
19
20outerZone();
21console.log(zone); // "Global Zone"

Scope Chain

When a variable isn't found in the current scope, JavaScript looks in parent scopes - this is the scope chain:

1const parkSecurity = 'Maximum'; // global
2
3function securityZone() {
4  const zoneLevel = 'High'; // securityZone scope
5
6  function enclosure() {
7    const enclosureStatus = 'Locked'; // enclosure scope
8
9    // Accesses variables from all parent scopes!
10    console.log(enclosureStatus); // "Locked" - own scope
11    console.log(zoneLevel);       // "High" - parent scope
12    console.log(parkSecurity);    // "Maximum" - global scope
13  }
14
15  enclosure();
16  // console.log(enclosureStatus); // ReferenceError - can't go down!
17}

Closures

A closure occurs when a function "remembers" variables from its outer scope, even after that outer function has finished executing:

1function createCounter(startValue = 0) {
2  let count = startValue; // this variable "lives" with the closure
3
4  return {
5    increment() { count++; return count; },
6    decrement() { count--; return count; },
7    getValue() { return count; }
8  };
9}
10
11const dinoCounter = createCounter(10);
12console.log(dinoCounter.increment()); // 11
13console.log(dinoCounter.increment()); // 12
14console.log(dinoCounter.decrement()); // 11
15console.log(dinoCounter.getValue());  // 11
16
17// Each counter has its own independent `count`!
18const raptorCounter = createCounter(5);
19console.log(raptorCounter.increment()); // 6
20console.log(dinoCounter.getValue());    // 11 - unchanged!

IIFE - Immediately Invoked Function Expression

IIFE creates a private scope to protect variables from the global namespace:

1// Park management system isolated in its own scope
2const parkSystem = (function() {
3  // Private variables - not accessible from outside
4  let _alertLevel = 0;
5  let _incidents = [];
6
7  // Private functions
8  function _logIncident(incident) {
9    _incidents.push({ ...incident, timestamp: new Date() });
10  }
11
12  // Public API
13  return {
14    raiseAlert(level, description) {
15      _alertLevel = Math.max(_alertLevel, level);
16      _logIncident({ level, description });
17      console.log(`Alert level: ${_alertLevel} - ${description}`);
18    },
19
20    getStatus() {
21      return {
22        alertLevel: _alertLevel,
23        incidentCount: _incidents.length
24      };
25    }
26  };
27})(); // Immediately invoked!
28
29parkSystem.raiseAlert(3, 'Raptor escaped');
30parkSystem.raiseAlert(5, 'T-Rex fence breach');
31console.log(parkSystem.getStatus());
32// { alertLevel: 5, incidentCount: 2 }
33
34// console.log(_alertLevel); // ReferenceError - private!

var vs let vs const - Key Differences

Now let's better understand the differences between these three keywords:

  1. var
    :

    • Function-scoped or global-scoped, but NOT block-scoped
    • Can be redeclared multiple times
    • Can be reassigned
    • Hoisted with an
      undefined
      value
  2. let
    :

    • Block-scoped
    • Cannot be redeclared in the same scope
    • Can be reassigned
    • Hoisted without a value (temporal dead zone)
  3. const
    :

    • Block-scoped
    • Cannot be redeclared in the same scope
    • Cannot be reassigned (but object properties can be modified)
    • Hoisted without a value (temporal dead zone)
1function demonstrateVariables() {
2  // var
3  var dinosaur = "T-Rex";
4  var dinosaur = "Velociraptor"; // OK - can redeclare
5  dinosaur = "Triceratops";      // OK - can reassign
6
7  // let
8  let park = "Jurassic Park";
9  // let park = "Dino World";    // Error - cannot redeclare
10  park = "Jurassic World";       // OK - can reassign
11
12  // const
13  const island = "Isla Nublar";
14  // const island = "Isla Sorna"; // Error - cannot redeclare
15  // island = "Another Island";   // Error - cannot reassign
16
17  // But properties of const objects CAN be modified
18  const parkSystem = {
19    mainPower: true,
20    backupGenerator: false
21  };
22
23  // This is allowed
24  parkSystem.mainPower = false;
25  parkSystem.backupGenerator = true;
26
27  console.log(parkSystem); // { mainPower: false, backupGenerator: true }
28}
29
30demonstrateVariables();

Best Practices

  1. Avoid global variables - just like in a real park, the more public areas there are, the harder they are to monitor and secure.

  2. Prefer

    const
    over
    let
    , and
    let
    over
    var
    - most variables should not change their values, and if they do, they should have the smallest possible scope.

  3. Use block scope - limit variable visibility to the smallest possible scope.

  4. Use the module pattern - encapsulate variables and functions in modules, exposing only the necessary API.

  5. Be aware of closures - closures are a powerful tool, but can lead to memory leaks if used improperly.

"Variable scope in JavaScript is like security clearance levels in Jurassic Park" - says Dr. Rex. "A ranger in zone A can see everything in that zone, but can also access global park information. A visitor can only see public areas. The important thing is that information doesn't flow upward - inner zones can't see outer zone details!"

Go to CodeWorlds