跳转到内容

作用域与闭包

来自代码酷

模板:Note

作用域(Scope)[编辑 | 编辑源代码]

作用域是程序中变量、函数和对象的可访问性规则,决定了代码中变量的可见性。JavaScript采用词法作用域(静态作用域),即作用域在代码编写时确定。

作用域类型[编辑 | 编辑源代码]

全局作用域[编辑 | 编辑源代码]

全局作用域中定义的变量可在代码任何位置访问:

var globalVar = "全局变量";
function showGlobal() {
    console.log(globalVar); // 可访问
}
showGlobal(); // 输出: "全局变量"

函数作用域[编辑 | 编辑源代码]

通过var声明的变量具有函数作用域:

function demoFunctionScope() {
    var localVar = "局部变量";
    console.log(localVar); // 可访问
}
demoFunctionScope(); // 输出: "局部变量"
console.log(localVar); // 报错: localVar未定义

块级作用域[编辑 | 编辑源代码]

ES6引入的letconst支持块级作用域:

if (true) {
    let blockVar = "块级变量";
    console.log(blockVar); // 输出: "块级变量"
}
console.log(blockVar); // 报错: blockVar未定义

作用域链[编辑 | 编辑源代码]

JavaScript通过作用域链逐级向上查找变量:

graph LR A[当前作用域] --> B[父级作用域] B --> C[全局作用域]

示例:

var outerVar = "外部";
function outer() {
    var middleVar = "中间";
    function inner() {
        console.log(outerVar, middleVar); // 依次向上查找
    }
    inner(); // 输出: "外部 中间"
}
outer();

闭包(Closure)[编辑 | 编辑源代码]

闭包是函数与其词法环境的组合,即使函数在其词法作用域外执行,仍能访问原始作用域的变量。

闭包原理[编辑 | 编辑源代码]

闭包的形成需要三个条件:

  1. 函数嵌套
  2. 内部函数引用外部变量
  3. 内部函数在外部作用域被调用

示例:

function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}
const counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2

闭包的实际应用[编辑 | 编辑源代码]

数据封装[编辑 | 编辑源代码]

实现私有变量:

function createBankAccount(initialBalance) {
    let balance = initialBalance;
    return {
        deposit: function(amount) {
            balance += amount;
            return balance;
        },
        withdraw: function(amount) {
            if (amount > balance) return "余额不足";
            balance -= amount;
            return balance;
        }
    };
}
const account = createBankAccount(1000);
console.log(account.deposit(500)); // 输出: 1500
console.log(account.withdraw(200)); // 输出: 1300

模块模式[编辑 | 编辑源代码]

现代模块系统的基础:

const calculator = (function() {
    function add(a, b) { return a + b; }
    function multiply(a, b) { return a * b; }
    return { add, multiply };
})();
console.log(calculator.add(2, 3)); // 输出: 5

性能注意事项[编辑 | 编辑源代码]

闭包可能导致内存泄漏,需注意:

  • 不再使用的闭包应及时解除引用
  • 避免在循环中创建闭包

进阶概念[编辑 | 编辑源代码]

词法环境(Lexical Environment)[编辑 | 编辑源代码]

JavaScript引擎内部通过词法环境实现作用域,包含:

  • 环境记录(存储变量)
  • 对外部词法环境的引用

数学表示: Environment=Record,Outer

IIFE(立即调用函数表达式)[编辑 | 编辑源代码]

创建独立作用域的经典模式:

(function() {
    var privateVar = "私有";
    console.log(privateVar); // 输出: "私有"
})();
// console.log(privateVar); // 报错

常见面试题分析[编辑 | 编辑源代码]

循环中的闭包问题[编辑 | 编辑源代码]

经典问题及解决方案:

// 问题代码
for (var i = 1; i <= 3; i++) {
    setTimeout(function() {
        console.log(i); // 输出三次 4
    }, 100);
}

// 解决方案1:使用IIFE
for (var i = 1; i <= 3; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j); // 输出 1, 2, 3
        }, 100);
    })(i);
}

// 解决方案2:使用let
for (let i = 1; i <= 3; i++) {
    setTimeout(function() {
        console.log(i); // 输出 1, 2, 3
    }, 100);
}

模板:Tip