Skip to content

作用域与闭包

题目1: 什么是闭包?如何使用闭包?

答案: 闭包是指有权访问另一个函数作用域中的变量的函数。简单来说,闭包就是一个函数以及其捆绑的周边环境状态的引用的组合。

闭包的使用方法:

  1. 创建私有变量
  2. 实现数据隐藏和封装
  3. 实现函数工厂
  4. 实现回调和高阶函数

扩展:

javascript
function createCounter() {
    let count = 0;
    return function() {
        return ++count;
    }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

题目2: 解释JavaScript中的作用域和作用域链。

答案: 作用域是指程序中定义变量的区域,它决定了变量的可访问性。JavaScript中主要有以下几种作用域:

  1. 全局作用域
  2. 函数作用域
  3. 块级作用域(ES6引入)

作用域链是JavaScript引擎查找变量时所遵循的结构。当在某个作用域中使用变量时,如果该作用域中没有定义这个变量,JavaScript引擎会沿着作用域链向上查找,直到找到该变量或到达全局作用域。

扩展: 作用域链的形成与函数定义时的环境有关,而不是调用时的环境。这就是词法作用域(Lexical Scope)的概念。

题目3: 什么是块级作用域?使用letconst的好处?

答案: 块级作用域是指变量在声明它的代码块内部才能访问。在ES6之前,JavaScript只有全局作用域和函数作用域。ES6引入的letconst关键字允许声明块级作用域的变量。

使用letconst的好处:

  1. 避免变量提升带来的问题
  2. 防止变量污染全局作用域
  3. 更好地控制变量的可见性和生命周期
  4. const可以声明常量,提高代码的可读性和可维护性

扩展:

javascript
if (true) {
    var x = 10;
    let y = 20;
}
console.log(x); // 10
console.log(y); // ReferenceError: y is not defined

题目4: 如何理解函数声明与函数表达式的区别?

答案: 函数声明:使用function关键字声明一个函数。 函数表达式:将一个函数赋值给一个变量。

主要区别:

  1. 函数声明会被提升(hoisted),可以在声明之前调用。函数表达式不会被提升。
  2. 函数声明在条件语句中的行为可能因浏览器而异,不推荐在条件语句中使用函数声明。
  3. 函数表达式可以是匿名的,函数声明必须有名字。

扩展:

javascript
// 函数声明
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的主要用途:

  1. 创建私有作用域,避免变量污染全局命名空间
  2. 模块化代码
  3. 避免变量提升带来的问题
  4. 在循环中创建闭包

扩展:

javascript
(function() {
    var privateVar = 'I am private';
    console.log(privateVar);
})();

console.log(typeof privateVar); // undefined

题目6: 解释一下变量提升(Hoisting)。

答案: 变量提升是JavaScript中的一种机制,它将变量和函数的声明移动到它们所在作用域的顶部。这意味着无论在哪里声明变量和函数,它们都会被视为在当前作用域的开头声明。

需要注意的是:

  1. 只有声明会被提升,初始化不会
  2. 函数声明会被完整地提升
  3. letconst声明的变量不会被提升

扩展:

javascript
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)?

答案: 临时死区是指使用letconst声明的变量在声明之前不能被访问的区域。在变量声明之前的执行瞬间被称为临时死区。

特点:

  1. letconst声明的变量不会被提升
  2. 在声明之前访问变量会抛出ReferenceError
  3. typeof操作符在TDZ中也会抛出ReferenceError

扩展:

javascript
console.log(x); // ReferenceError
let x = 5;

{
    console.log(y); // ReferenceError
    const y = 10;
}

题目8: 如何实现私有变量和方法?

答案: JavaScript中实现私有变量和方法的常用方式:

  1. 使用闭包
  2. 使用Symbol
  3. 使用WeakMap
  4. 使用#前缀(ES2022新特性)

扩展:

javascript
// 使用闭包
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)。

答案: 词法作用域,也称为静态作用域,是指作用域是由代码中函数声明的位置来决定的,而不是由函数调用的位置决定。

特点:

  1. 嵌套的函数可以访问在其外部作用域中声明的变量
  2. 内部函数包含其外部函数的作用域
  3. 作用域链是在函数定义时确定的,而不是在函数调用时

扩展:

javascript
let x = 10;

function foo() {
    let y = 20;
    function bar() {
        console.log(x, y); // 可以访问外部作用域的x和y
    }
    bar();
}

foo(); // 输出:10 20

题目10: 什么是模块模式(Module Pattern)?它如何利用闭包?

答案: 模块模式是一种使用闭包来创建私有作用域和组织代码的方法。它允许我们创建公共和私有方法与变量,有助于避免全局作用域的污染。

模块模式的特点:

  1. 封装:隐藏实现细节
  2. 暴露公共API
  3. 创建私有变量和方法

扩展:

javascript
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和闭包来创建一个私有作用域,只暴露了我们想要公开的部分。