作用域与闭包
题目1: 什么是闭包?如何使用闭包?
答案: 闭包是指有权访问另一个函数作用域中的变量的函数。简单来说,闭包就是一个函数以及其捆绑的周边环境状态的引用的组合。
闭包的使用方法:
- 创建私有变量
- 实现数据隐藏和封装
- 实现函数工厂
- 实现回调和高阶函数
扩展:
function createCounter() {
let count = 0;
return function() {
return ++count;
}
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
题目2: 解释JavaScript中的作用域和作用域链。
答案: 作用域是指程序中定义变量的区域,它决定了变量的可访问性。JavaScript中主要有以下几种作用域:
- 全局作用域
- 函数作用域
- 块级作用域(ES6引入)
作用域链是JavaScript引擎查找变量时所遵循的结构。当在某个作用域中使用变量时,如果该作用域中没有定义这个变量,JavaScript引擎会沿着作用域链向上查找,直到找到该变量或到达全局作用域。
扩展: 作用域链的形成与函数定义时的环境有关,而不是调用时的环境。这就是词法作用域(Lexical Scope)的概念。
题目3: 什么是块级作用域?使用let
和const
的好处?
答案: 块级作用域是指变量在声明它的代码块内部才能访问。在ES6之前,JavaScript只有全局作用域和函数作用域。ES6引入的let
和const
关键字允许声明块级作用域的变量。
使用let
和const
的好处:
- 避免变量提升带来的问题
- 防止变量污染全局作用域
- 更好地控制变量的可见性和生命周期
const
可以声明常量,提高代码的可读性和可维护性
扩展:
if (true) {
var x = 10;
let y = 20;
}
console.log(x); // 10
console.log(y); // ReferenceError: y is not defined
题目4: 如何理解函数声明与函数表达式的区别?
答案: 函数声明:使用function
关键字声明一个函数。 函数表达式:将一个函数赋值给一个变量。
主要区别:
- 函数声明会被提升(hoisted),可以在声明之前调用。函数表达式不会被提升。
- 函数声明在条件语句中的行为可能因浏览器而异,不推荐在条件语句中使用函数声明。
- 函数表达式可以是匿名的,函数声明必须有名字。
扩展:
// 函数声明
foo(); // 可以正常运行
function foo() {
console.log('Hello');
}
// 函数表达式
bar(); // TypeError: bar is not a function
var bar = function() {
console.log('Hello');
};
题目5: 什么是IIFE(立即调用函数表达式)?它有什么用途?
答案: IIFE(Immediately Invoked Function Expression)是一个在定义后立即执行的JavaScript函数。
IIFE的主要用途:
- 创建私有作用域,避免变量污染全局命名空间
- 模块化代码
- 避免变量提升带来的问题
- 在循环中创建闭包
扩展:
(function() {
var privateVar = 'I am private';
console.log(privateVar);
})();
console.log(typeof privateVar); // undefined
题目6: 解释一下变量提升(Hoisting)。
答案: 变量提升是JavaScript中的一种机制,它将变量和函数的声明移动到它们所在作用域的顶部。这意味着无论在哪里声明变量和函数,它们都会被视为在当前作用域的开头声明。
需要注意的是:
- 只有声明会被提升,初始化不会
- 函数声明会被完整地提升
let
和const
声明的变量不会被提升
扩展:
console.log(x); // undefined
var x = 5;
foo(); // "Hello"
function foo() {
console.log("Hello");
}
bar(); // TypeError: bar is not a function
var bar = function() {
console.log("Hello");
};
题目7: 什么是临时死区(Temporal Dead Zone,TDZ)?
答案: 临时死区是指使用let
和const
声明的变量在声明之前不能被访问的区域。在变量声明之前的执行瞬间被称为临时死区。
特点:
let
和const
声明的变量不会被提升- 在声明之前访问变量会抛出
ReferenceError
typeof
操作符在TDZ中也会抛出ReferenceError
扩展:
console.log(x); // ReferenceError
let x = 5;
{
console.log(y); // ReferenceError
const y = 10;
}
题目8: 如何实现私有变量和方法?
答案: JavaScript中实现私有变量和方法的常用方式:
- 使用闭包
- 使用Symbol
- 使用WeakMap
- 使用#前缀(ES2022新特性)
扩展:
// 使用闭包
function createPerson(name) {
let age = 0;
return {
getName: () => name,
getAge: () => age,
setAge: (newAge) => { age = newAge; }
};
}
// 使用Symbol
const Person = (function() {
const ageSymbol = Symbol('age');
return class {
constructor(name) {
this.name = name;
this[ageSymbol] = 0;
}
getAge() { return this[ageSymbol]; }
setAge(age) { this[ageSymbol] = age; }
};
})();
// 使用#前缀(ES2022)
class Person {
#age = 0;
constructor(name) {
this.name = name;
}
getAge() { return this.#age; }
setAge(age) { this.#age = age; }
}
题目9: 解释一下词法作用域(Lexical Scope)。
答案: 词法作用域,也称为静态作用域,是指作用域是由代码中函数声明的位置来决定的,而不是由函数调用的位置决定。
特点:
- 嵌套的函数可以访问在其外部作用域中声明的变量
- 内部函数包含其外部函数的作用域
- 作用域链是在函数定义时确定的,而不是在函数调用时
扩展:
let x = 10;
function foo() {
let y = 20;
function bar() {
console.log(x, y); // 可以访问外部作用域的x和y
}
bar();
}
foo(); // 输出:10 20
题目10: 什么是模块模式(Module Pattern)?它如何利用闭包?
答案: 模块模式是一种使用闭包来创建私有作用域和组织代码的方法。它允许我们创建公共和私有方法与变量,有助于避免全局作用域的污染。
模块模式的特点:
- 封装:隐藏实现细节
- 暴露公共API
- 创建私有变量和方法
扩展:
const myModule = (function() {
let privateVar = 0;
function privateFunction() {
console.log('This is private');
}
return {
publicVar: 1,
publicFunction: function() {
privateVar++;
privateFunction();
console.log('Private var:', privateVar);
}
};
})();
myModule.publicFunction(); // 输出:This is private \n Private var: 1
console.log(myModule.publicVar); // 1
console.log(myModule.privateVar); // undefined
这个模块模式使用了IIFE和闭包来创建一个私有作用域,只暴露了我们想要公开的部分。