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.
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.
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.
'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.
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.
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.
'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.
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.
{
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.
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.
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