闭包与作用域

时游大约 2 分钟

闭包与作用域

作用域 (Scope)

作用域规定了变量能够被访问的范围。

  • 全局作用域: 代码中任何地方都能访问。
  • 函数作用域: 只能在函数内部访问。
  • 块级作用域 (ES6): letconst{} 中声明的变量只能在块内部访问。
let globalVar = "Global";

function outer() {
    let outerVar = "Outer";
    
    if (true) {
        let blockVar = "Block";
        console.log(blockVar); // Block
    }
    // console.log(blockVar); // Error
    
    function inner() {
        console.log(outerVar); // Outer (作用域链)
        console.log(globalVar); // Global
    }
    inner();
}

闭包 (Closure)

闭包是指有权访问另一个函数作用域中变量的函数。通常是在一个函数内部创建另一个函数。

核心特性: 即使外部函数已经执行完毕,闭包依然可以访问外部函数的变量(变量依然保存在内存中)。

基础示例

function createCounter() {
    let count = 0; // 私有变量
    
    return function() {
        count++;
        return count;
    };
}

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

const counter2 = createCounter();
console.log(counter2()); // 1 (独立的闭包环境)

应用场景

  1. 模拟私有变量/模块化:
const User = (function() {
    let _name = "Unknown"; // 私有变量
    
    return {
        getName: function() {
            return _name;
        },
        setName: function(newName) {
            _name = newName;
        }
    };
})();

console.log(User.getName()); // Unknown
User.setName("Tom");
console.log(User.getName()); // Tom
// console.log(User._name); // undefined
  1. 柯里化 (Currying):
function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}

const add5 = makeAdder(5);
console.log(add5(2)); // 7
  1. 循环中的陷阱:
// 问题代码
for (var i = 1; i <= 3; i++) {
    setTimeout(function() {
        console.log(i); // 输出三次 4
    }, 1000);
}

// 解决方案1: IIFE (立即执行函数) 创建闭包
for (var i = 1; i <= 3; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j); // 1, 2, 3
        }, 1000);
    })(i);
}

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

内存泄漏

由于闭包会引用外部函数的变量,导致这些变量无法被垃圾回收机制回收。如果闭包使用不当,可能会导致内存泄漏。

解决方法: 在不需要使用闭包时,将引用闭包的变量设置为 null

let closure = createCounter();
// ... 使用闭包
closure = null; // 释放内存

this 指向

this 的指向是在函数被调用时确定的。

  1. 默认绑定: 独立函数调用,指向全局对象 (严格模式下为 undefined)。
  2. 隐式绑定: 对象方法调用,指向调用该方法的对象。
  3. 显式绑定: call, apply, bind,指向指定的对象。
  4. new 绑定: 指向新创建的实例对象。
  5. 箭头函数: 继承外层作用域的 this
const obj = {
    name: "Alice",
    say: function() {
        console.log(this.name);
    },
    sayArrow: () => {
        console.log(this.name);
    }
};

obj.say(); // Alice
obj.sayArrow(); // undefined (浏览器中指向 window.name)

const func = obj.say;
func(); // undefined (默认绑定)

const boundFunc = obj.say.bind({ name: "Bob" });
boundFunc(); // Bob (显式绑定)

优先级: new > 显式绑定 (bind/call/apply) > 隐式绑定 (obj.method) > 默认绑定。

上次编辑于:
贡献者: 15327360835
Loading...