Exercise

The catch block's parameter is scoped to the catch block itself. However, variables declared with var inside try or catch are function-scoped, while let and const remain block-scoped.

javascript

try {
    var x = 10;
    let y = 20;
    throw new Error('test');
} catch (err) {
    console.log(x);   // Output: 10 (var is function-scoped)
    console.log(err);  // Output: Error: test
}
console.log(x);   // Output: 10
console.log(y);   // ReferenceError: y is not defined
console.log(err); // ReferenceError: err is not defined

A switch statement shares a single block scope across all case clauses. Variables declared with let or const in one case are visible in other cases, which can cause errors. Wrapping cases in curly braces creates separate block scopes.

javascript

switch (1) {
    case 1:
        let value = 'one';
        break;
    case 2:
        let value = 'two'; // SyntaxError: 'value' has already been declared
        break;
}

// Fix: wrap each case in its own block
switch (1) {
    case 1: {
        let value = 'one';
        console.log(value); // Output: one
        break;
    }
    case 2: {
        let value = 'two';
        break;
    }
}

In strict mode, assigning to an undeclared variable throws a ReferenceError instead of creating a global variable. It also prevents some scope-related issues like using the with statement.

javascript

'use strict';

function setCity() {
    city = 'Paris'; // ReferenceError: city is not defined
}
setCity();

A nested function can both read and modify variables from its parent scope. Changes made by the inner function are visible in the outer function because they share the same variable binding.

javascript

function outer() {
    var count = 0;
    
    function increment() {
        count++;
    }
    
    increment();
    increment();
    console.log(count); // Output: 2
}
outer();

Arrow functions inherit scope the same way as regular functions for variable access. Both create their own scope for locally declared variables. The key difference is that arrow functions do not have their own this binding - they inherit this from the enclosing scope.

javascript

var name = 'global';

const obj = {
    name: 'object',
    regular: function() {
        console.log(this.name); // Output: object
    },
    arrow: () => {
        console.log(this.name); // Output: global (inherits outer this)
    }
};

obj.regular();
obj.arrow();

Function declarations inside blocks behave inconsistently across JavaScript environments. In strict mode, they are block-scoped. In non-strict mode, the behavior varies. It is recommended to use function expressions with let or const inside blocks for predictable scope behavior.

javascript

'use strict';

if (true) {
    function blockFunc() {
        return 'I am block-scoped in strict mode';
    }
    console.log(blockFunc()); // Output: I am block-scoped in strict mode
}
console.log(typeof blockFunc); // Output: undefined

Default parameters create their own intermediate scope between the outer scope and the function body scope. Each default parameter can reference parameters defined before it but not after it.

javascript

var x = 10;

function example(a = x, b = a * 2) {
    var x = 20;
    console.log(a); // Output: 10 (from outer scope, not inner var)
    console.log(b); // Output: 20 (a * 2)
}
example();

Class declarations are block-scoped like let. Inside a class, methods do not share a common scope for local variables. Each method has its own function scope. Class fields (properties) are accessible through this, not through scope.

javascript

{
    class Animal {
        constructor(name) {
            this.name = name;
        }
        speak() {
            var sound = 'generic';
            console.log(this.name + ' makes a ' + sound + ' sound');
        }
    }
    
    const dog = new Animal('Dog');
    dog.speak(); // Output: Dog makes a generic sound
}
console.log(typeof Animal); // Output: undefined (block-scoped)

Destructured variables follow the same scope rules as their declaration keyword (var, let, or const). Variables created through destructuring in a block with let or const are block-scoped.

javascript

if (true) {
    const { name, age } = { name: 'Alice', age: 25 };
    console.log(name); // Output: Alice
    console.log(age);  // Output: 25
}
console.log(name); // ReferenceError: name is not defined

When a function is passed as a callback, it retains access to the scope where it was defined (lexical scope), not where it is called. This is because JavaScript uses lexical scoping.

javascript

function createLogger() {
    var prefix = '[LOG]';
    
    return function(message) {
        console.log(prefix + ' ' + message);
    };
}

function execute(callback) {
    var prefix = '[ERROR]'; // this does NOT affect the callback
    callback('Hello');
}

execute(createLogger()); // Output: [LOG] Hello