返回首页

03-闭包与作用域链

分类:03-异步编程进阶
发布于:
阅读时间:101 分钟

闭包与作用域链

📋 学习目标

  • 深入理解闭包的概念和形成原理
  • 掌握作用域链的工作机制
  • 学会闭包的实际应用场景
  • 理解闭包的内存管理和性能影响
  • 掌握常见闭包陷阱的解决方案

🎯 作用域基础

1. 作用域类型

// 全局作用域(Global Scope)
var globalVar = 'I am global';

function globalFunction() {
    console.log(globalVar); // 可以访问全局变量
}

// 函数作用域(Function Scope)
function outerFunction() {
    var outerVar = 'I am outer';

    function innerFunction() {
        var innerVar = 'I am inner';
        console.log(globalVar); // 可以访问全局变量
        console.log(outerVar);  // 可以访问外层函数变量
        console.log(innerVar);  // 可以访问自己的变量
    }

    innerFunction();
    // console.log(innerVar); // ReferenceError: 无法访问内层函数变量
}

outerFunction();
// console.log(outerVar); // ReferenceError: 无法访问函数内部变量

// 块级作用域(Block Scope)- ES6
function blockScopeDemo() {
    if (true) {
        let blockVar = 'I am block scoped';
        const blockConst = 'I am also block scoped';

        console.log(blockVar); // 可以访问
    }

    // console.log(blockVar); // ReferenceError: 无法访问块级变量
}

blockScopeDemo();

// 作用域嵌套
var level1 = 'global';

function level1() {
    var level1 = 'level1';

    function level2() {
        var level2 = 'level2';

        function level3() {
            var level3 = 'level3';

            // 作用域链查找
            console.log(level3); // 'level3' (在当前作用域找到)
            console.log(level2);  // 'level2' (在外层作用域找到)
            console.log(level1);  // 'level1' (再外层作用域找到)
            // console.log(global); // ReferenceError (未定义)
        }

        level3();
    }

    level2();
}

level1();

2. 作用域链查找机制

// 作用域链:从内到外查找变量
var name = 'Global';

function outer() {
    var name = 'Outer';

    function inner() {
        var name = 'Inner';
        console.log(name); // 'Inner' (在内层作用域找到)
    }

    function inner2() {
        console.log(name); // 'Outer' (在outer作用域找到)
    }

    inner();
    inner2();
}

outer();

// 变量遮蔽(Variable Shadowing)
var value = 'Global value';

function testShadowing() {
    var value = 'Local value';
    console.log(value); // 'Local value' (局部变量遮蔽了全局变量)

    function nested() {
        var value = 'Nested value';
        console.log(value); // 'Nested value' (内层变量遮蔽了外层变量)
    }

    nested();
}

testShadowing();
console.log(value); // 'Global value'

🔐 闭包(Closure)

1. 闭包的定义和原理

// 闭包:函数能够访问其外部作用域的变量,即使外部函数已经执行完毕
function outerFunction(x) {
    // 外部函数的局部变量
    var outerVariable = 'I am from outer';

    // 内部函数形成闭包
    function innerFunction(y) {
        // 可以访问外部函数的变量
        console.log(outerVariable); // 'I am from outer'
        console.log(x); // 外部参数
        console.log(y); // 内部参数

        return x + y;
    }

    return innerFunction; // 返回内部函数
}

// 创建闭包
const closureFunction = outerFunction(10);

// 即使outerFunction已经执行完毕,innerFunction仍然可以访问其变量
console.log(closureFunction(5)); // 15 (10 + 5)

// 每次调用outerFunction都会创建新的闭包
const anotherClosure = outerFunction(20);
console.log(anotherClosure(3)); // 23 (20 + 3)

2. 闭包的经典示例

// 示例1:计数器闭包
function createCounter() {
    let count = 0; // 私有变量

    return {
        increment: function() {
            count++;
            return count;
        },

        decrement: function() {
            count--;
            return count;
        },

        getCount: function() {
            return count;
        },

        reset: function() {
            count = 0;
            return count;
        }
    };
}

const counter1 = createCounter();
console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.getCount());  // 2

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

// 示例2:私有变量
function createPerson(name) {
    // 私有变量
    let _name = name;
    let _age = 0;

    return {
        getName: function() {
            return _name;
        },

        getAge: function() {
            return _age;
        },

        setAge: function(newAge) {
            if (newAge >= 0 && newAge < 150) {
                _age = newAge;
            } else {
                throw new Error('Invalid age');
            }
        },

        celebrateBirthday: function() {
            _age++;
            return `Happy ${_age}th birthday, ${_name}!`;
        }
    };
}

const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
console.log(person.celebrateBirthday()); // 'Happy 1st birthday, Alice!'
console.log(person.getAge()); // 1
// person._name = 'Bob'; // 无法直接访问私有变量

3. 闭包与循环

// 陷阱:循环中的闭包
function createFunctions() {
    const functions = [];

    for (var i = 0; i < 3; i++) {
        functions[i] = function() {
            return i; // 所有函数都引用同一个i
        };
    }

    return functions;
}

const functions = createFunctions();
console.log(functions[0]()); // 3
console.log(functions[1]()); // 3
console.log(functions[2]()); // 3

// 解决方案1:使用IIFE
function createFunctionsFixed1() {
    const functions = [];

    for (var i = 0; i < 3; i++) {
        functions[i] = (function(j) {
            return function() {
                return j; // 每个函数捕获不同的j
            };
        })(i);
    }

    return functions;
}

const fixedFunctions1 = createFunctionsFixed1();
console.log(fixedFunctions1[0]()); // 0
console.log(fixedFunctions1[1]()); // 1
console.log(fixedFunctions1[2]()); // 2

// 解决方案2:使用let(块级作用域)
function createFunctionsFixed2() {
    const functions = [];

    for (let i = 0; i < 3; i++) {
        functions[i] = function() {
            return i; // 每次循环创建新的块级作用域
        };
    }

    return functions;
}

const fixedFunctions2 = createFunctionsFixed2();
console.log(fixedFunctions2[0]()); // 0
console.log(fixedFunctions2[1]()); // 1
console.log(fixedFunctions2[2]()); // 2

🚀 闭包的实际应用

1. 函数工厂

// 创建特定功能的函数
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

console.log(double(5));   // 10
console.log(triple(5));   // 15
console.log(quadruple(5)); // 20

// 创建验证器
function createValidator(rules) {
    return function(data) {
        const errors = [];

        for (const [field, rule] of Object.entries(rules)) {
            if (!rule.test(data[field])) {
                errors.push(`${field}: ${rule.message}`);
            }
        }

        return {
            isValid: errors.length === 0,
            errors
        };
    };
}

const userValidator = createValidator({
    name: {
        test: name => name && name.length >= 2,
        message: 'Name must be at least 2 characters'
    },
    email: {
        test: email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
        message: 'Invalid email format'
    },
    age: {
        test: age => age >= 18 && age <= 120,
        message: 'Age must be between 18 and 120'
    }
});

const userData = { name: 'A', email: 'invalid', age: 25 };
const validationResult = userValidator(userData);
console.log(validationResult);
// {
//   isValid: false,
//   errors: ['Name must be at least 2 characters', 'Invalid email format']
// }

2. 事件处理器

// 闭包在事件处理中的应用
function setupButtons() {
    const buttons = document.querySelectorAll('button');
    const messages = ['Button 1 clicked', 'Button 2 clicked', 'Button 3 clicked'];

    buttons.forEach((button, index) => {
        button.addEventListener('click', function() {
            // 闭包保存了index和messages
            console.log(messages[index]);
            alert(messages[index]);
        });
    });
}

// 实际应用:点击计数器
function createClickCounter(maxClicks = 5) {
    let clickCount = 0;

    return function() {
        clickCount++;

        if (clickCount <= maxClicks) {
            console.log(`Click ${clickCount} of ${maxClicks}`);
            return `You clicked ${clickCount} times`;
        } else {
            console.log('Maximum clicks reached');
            return 'Maximum clicks reached';
        }
    };
}

const clickCounter = createClickCounter(3);
console.log(clickCounter()); // 'You clicked 1 times'
console.log(clickCounter()); // 'You clicked 2 times'
console.log(clickCounter()); // 'You clicked 3 times'
console.log(clickCounter()); // 'Maximum clicks reached'

3. 数据缓存

// 函数结果缓存
function memoize(fn) {
    const cache = new Map();

    return function(...args) {
        // 创建缓存键
        const key = JSON.stringify(args);

        // 检查缓存
        if (cache.has(key)) {
            console.log('From cache:', key);
            return cache.get(key);
        }

        // 计算结果并缓存
        const result = fn.apply(this, args);
        cache.set(key, result);
        console.log('Computed and cached:', key);

        return result;
    };
}

// 昂贵的计算函数
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 创建记忆化版本
const memoizedFibonacci = memoize(fibonacci);

console.log(memoizedFibonacci(10)); // 计算并缓存
console.log(memoizedFibonacci(10)); // 从缓存获取
console.log(memoizedFibonacci(15)); // 计算并缓存
console.log(memoizedFibonacci(10)); // 从缓存获取

// API请求缓存
function createApiCache() {
    const cache = new Map();
    const pending = new Map();

    return async function(url, options = {}) {
        const cacheKey = `${url}:${JSON.stringify(options)}`;

        // 检查缓存
        if (cache.has(cacheKey)) {
            return cache.get(cacheKey);
        }

        // 检查是否有正在进行的请求
        if (pending.has(cacheKey)) {
            return pending.get(cacheKey);
        }

        // 发起新请求
        const promise = fetch(url, options)
            .then(response => response.json())
            .then(data => {
                cache.set(cacheKey, data);
                pending.delete(cacheKey);
                return data;
            })
            .catch(error => {
                pending.delete(cacheKey);
                throw error;
            });

        pending.set(cacheKey, promise);
        return promise;
    };
}

const apiCache = createApiCache();

// 使用缓存API
async function fetchUserData(userId) {
    return await apiCache(`/api/users/${userId}`);
}

4. 模块模式

// 使用闭包创建模块
const ShoppingModule = (function() {
    // 私有变量
    let cart = [];
    let total = 0;

    // 私有函数
    function calculateTotal() {
        total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
    }

    // 公共接口
    return {
        addItem: function(item) {
            cart.push(item);
            calculateTotal();
            console.log(`Added ${item.name} to cart`);
        },

        removeItem: function(itemId) {
            cart = cart.filter(item => item.id !== itemId);
            calculateTotal();
            console.log(`Removed item ${itemId} from cart`);
        },

        getCart: function() {
            return [...cart]; // 返回副本,防止外部修改
        },

        getTotal: function() {
            return total;
        },

        clear: function() {
            cart = [];
            total = 0;
            console.log('Cart cleared');
        }
    };
})();

// 使用模块
ShoppingModule.addItem({ id: 1, name: 'Book', price: 20, quantity: 2 });
ShoppingModule.addItem({ id: 2, name: 'Pen', price: 2, quantity: 5 });
console.log(ShoppingModule.getTotal()); // 50
console.log(ShoppingModule.getCart());

🔧 高级闭包技术

1. 柯里化(Currying)

// 柯里化:将多参数函数转换为单参数函数链
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...moreArgs) {
                return curried.apply(this, args.concat(moreArgs));
            };
        }
    };
}

// 使用柯里化
function add(a, b, c) {
    return a + b + c;
}

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6

// 实际应用:配置函数
function formatCurrency(symbol) {
    return function(separator) {
        return function(amount) {
            return `${symbol}${amount.toFixed(2).replace('.', separator)}`;
        };
    };
}

const formatUSD = formatCurrency('$');
const formatEuro = formatCurrency('€');

const formatUSDWithComma = formatUSD(',');
const formatEuroWithDot = formatEuro('.');

console.log(formatUSDWithComma(1234.56)); // '$1,234.56'
console.log(formatEuroWithDot(1234.56)); // '€1234,56'

2. 函数组合

// 函数组合:将多个函数连接起来
function compose(...fns) {
    return function(x) {
        return fns.reduceRight((acc, fn) => fn(acc), x);
    };
}

// 或者使用管道
function pipe(...fns) {
    return function(x) {
        return fns.reduce((acc, fn) => fn(acc), x);
    };
}

// 实用函数
const add = x => x + 1;
const multiply = x => x * 2;
const subtract = x => x - 3;

// 组合函数
const processNumber = compose(subtract, multiply, add);
// 等同于 subtract(multiply(add(x)))

console.log(processNumber(5)); // ((5 + 1) * 2) - 3 = 9

// 实际应用:数据处理管道
const users = [
    { name: 'Alice', age: 25, salary: 50000 },
    { name: 'Bob', age: 30, salary: 60000 },
    { names: 'Charlie', age: 35, salary: 70000 }
];

const filterByAge = minAge => users => users.filter(user => user.age >= minAge);
const sortBySalary = users => [...users].sort((a, b) => b.salary - a.salary);
const extractNames = users => users.map(user => user.name);

const getHighEarners = pipe(
    filterByAge(30),
    sortBySalary,
    extractNames
);

console.log(getHighEarners(users)); // ['Charlie', 'Bob']

3. 惰性求值

// 惰性求值:只在需要时计算值
function lazy(thunk) {
    let computed = false;
    let result;

    return function() {
        if (!computed) {
            result = thunk();
            computed = true;
        }
        return result;
    };
}

// 使用示例
function expensiveComputation() {
    console.log('Performing expensive computation...');
    return Math.random() * 1000;
}

const lazyValue = lazy(expensiveComputation);

console.log('Function created');
console.log(lazyValue()); // 第一次调用时执行计算
console.log(lazyValue()); // 第二次调用时返回缓存值

// 惰性序列
function* lazySequence() {
    let i = 0;
    while (true) {
        yield lazy(() => {
            console.log(`Computing value ${i}`);
            return i++;
        })();
    }
}

const sequence = lazySequence();
console.log(sequence.next().value()); // 计算并返回0
console.log(sequence.next().value()); // 计算并返回1

⚠️ 闭包的陷阱

1. 内存泄漏

// ❌ 问题:闭包导致内存泄漏
function createLeakyClosure() {
    const largeData = new Array(1000000).fill('data');

    return function() {
        // 闭包引用了largeData,导致largeData无法被垃圾回收
        return largeData.length;
    };
}

const leakyFunction = createLeakyClosure();
// largeData仍然存在于内存中,直到leakyFunction被释放

// ✅ 解决方案:只保留必要的数据
function createOptimizedClosure() {
    const largeData = new Array(1000000).fill('data');
    const necessaryInfo = largeData.length; // 只保留需要的数据

    return function() {
        return necessaryInfo;
    };
}

// 或者使用WeakMap
function createWeakMapClosure() {
    const cache = new WeakMap();

    return function(obj) {
        if (cache.has(obj)) {
            return cache.get(obj);
        }

        const result = processData(obj);
        cache.set(obj, result);
        return result;
    };
}

function processData(obj) {
    // 模拟处理大对象
    return Object.keys(obj).length;
}

2. this指向问题

// ❌ 问题:闭包中的this指向
const obj = {
    value: 42,
    getValue: function() {
        return function() {
            return this.value; // this指向全局对象或undefined
        };
    }
};

const getValueFunction = obj.getValue();
console.log(getValueFunction()); // undefined

// ✅ 解决方案1:保存this引用
const obj2 = {
    value: 42,
    getValue: function() {
        const self = this;
        return function() {
            return self.value;
        };
    }
};

const getValueFunction2 = obj2.getValue();
console.log(getValueFunction2()); // 42

// ✅ 解决方案2:使用箭头函数
const obj3 = {
    value: 42,
    getValue: function() {
        return () => this.value; // 箭头函数继承外层this
    }
};

const getValueFunction3 = obj3.getValue();
console.log(getValueFunction3()); // 42

3. 循环引用

// ❌ 问题:循环引用导致内存无法释放
function createCircularReference() {
    const obj1 = {};
    const obj2 = {};

    obj1.ref = obj2;
    obj2.ref = obj1; // 循环引用

    return function() {
        return obj1;
    };
}

// ✅ 解决方案:避免循环引用或使用WeakMap
function createSafeReference() {
    const obj1 = {};
    const obj2 = {};

    // 避免循环引用
    obj1.id = 'obj1';
    obj2.id = 'obj2';

    return function() {
        return { obj1, obj2 };
    };
}

📊 性能优化

1. 闭包性能考虑

// ❌ 避免在循环中创建闭包
function badExample(elements) {
    for (let i = 0; i < elements.length; i++) {
        elements[i].onclick = function() {
            // 每次循环都创建新的闭包
            console.log(i);
        };
    }
}

// ✅ 更好的方式:事件委托
function goodExample(container) {
    container.addEventListener('click', function(e) {
        if (e.target.classList.contains('item')) {
            const index = Array.from(container.children).indexOf(e.target);
            console.log(index);
        }
    });
}

// ✅ 或者使用data属性
function betterExample(elements) {
    elements.forEach((element, index) => {
        element.dataset.index = index;
        element.addEventListener('click', function() {
            console.log(this.dataset.index);
        });
    });
}

2. 闭包缓存策略

// 智能缓存:限制缓存大小
function createSmartCache(maxSize = 100) {
    const cache = new Map();
    const accessOrder = [];

    return function(key, computeFn) {
        if (cache.has(key)) {
            // 更新访问顺序
            const index = accessOrder.indexOf(key);
            if (index > -1) {
                accessOrder.splice(index, 1);
                accessOrder.push(key);
            }
            return cache.get(key);
        }

        // 计算新值
        const result = computeFn();

        // 检查缓存大小
        if (cache.size >= maxSize) {
            const oldestKey = accessOrder.shift();
            cache.delete(oldestKey);
        }

        cache.set(key, result);
        accessOrder.push(key);

        return result;
    };
}

const smartCache = createSmartCache(50);

function expensiveOperation(key) {
    console.log(`Computing for ${key}`);
    return key * 2;
}

console.log(smartCache('a', () => expensiveOperation('a'))); // 计算
console.log(smartCache('a', () => expensiveOperation('a'))); // 缓存

📝 最佳实践

1. 闭包使用原则

// ✅ 1. 明确闭包的用途
function createCounter(initialValue = 0) {
    let count = initialValue;

    return {
        increment: () => ++count,
        decrement: () => --count,
        reset: () => count = initialValue
    };
}

// ✅ 2. 避免不必要的闭包
function processData(data) {
    // 直接处理,不要创建不必要的闭包
    return data.map(item => item * 2);
}

// ❌ 避免这样写
function processDataBad(data) {
    const multiplier = 2;
    return data.map(function(item) {
        return item * multiplier; // 不必要的闭包
    });
}

2. 内存管理

// ✅ 及时清理不需要的引用
function createTempClosure() {
    const data = getLargeData();

    const processData = () => {
        const result = transformData(data);
        data = null; // 清理引用
        return result;
    };

    return processData;
}

// ✅ 使用WeakMap管理对象关联
const objectCache = new WeakMap();

function cacheObject(obj, processedData) {
    objectCache.set(obj, processedData);
    // 当obj被垃圾回收时,缓存条目也会被自动清理
}

3. 性能优化

// ✅ 减少闭包作用域
function optimizedFunction() {
    const constant = computeConstant(); // 只计算一次

    return function(variable) {
        // 闭包只引用必要的变量
        return constant + variable;
    };
}

// ✅ 使用函数工厂减少重复代码
function createValidator(schema) {
    const rules = Object.entries(schema);

    return function(data) {
        return rules.every(([field, rule]) => rule.test(data[field]));
    };
}

🎯 小结

  • 深入理解了闭包的概念和形成原理
  • 掌握了作用域链的工作机制
  • 学会了闭包的实际应用场景
  • 理解了闭包的内存管理和性能影响
  • 掌握了常见闭包陷阱的解决方案
  • 学会了高级闭包技术:柯里化、函数组合、惰性求值
  • 了解了闭包的性能优化和最佳实践

闭包是JavaScript中最强大的特性之一,正确使用闭包可以写出更优雅、更灵活的代码。


下一步学习节流与防抖