1. 闭包的基本概念

MDN对闭包的定义

  • 闭包是由捆绑起来(封闭的)的函数和函数周围状态(词法环境)的引用组合而成。
  • 换言之,闭包让函数能访问它的外部作用域。
  • 在 JavaScript 中,闭包会随着函数的创建而同时创建。
  • 狭义的理解:JavaScript中一个函数,如果访问了外层作用域中的变量,那么它就是一个闭包
// 基本闭包示例
function outerFunction(x) {
    // 外部函数的变量
    var outerVariable = x;
    
    // 内部函数
    function innerFunction(y) {
        // 内部函数可以访问外部函数的变量
        return outerVariable + y;
    }
    
    // 返回内部函数
    return innerFunction;
}

// 创建闭包
var closure = outerFunction(10);
console.log(closure(5)); // 输出: 15

// outerFunction已经执行完毕,但outerVariable仍然可以被访问

// 闭包示例1:定时器回调
function broadClosureExample() {
    var timerVariable = "Timer data";
    
    setTimeout(function() {
        console.log(timerVariable); // 访问外部作用域变量
    }, 1000);
    
    // 这个回调函数也是的闭包
}

// 闭包示例2:事件处理器
function eventClosureExample() {
    var eventData = "Event context";
    
    document.addEventListener('click', function() {
        console.log(eventData); // 访问外部变量
    });
    
    // 事件处理器也是的闭包
}

// 闭包示例3:数组方法回调
function arrayClosureExample() {
    var multiplier = 2;
    
    var numbers = [1, 2, 3, 4, 5];
    var doubled = numbers.map(function(num) {
        return num * multiplier; // 访问外部变量
    });
    
    // map回调函数也是的闭包
}

闭包的本质

闭包由两部分组成:

  1. 函数:内部函数本身
  2. 词法环境:函数创建时的作用域链
// 闭包的内存结构示意
function createClosure() {
    var localVar = "I'm local";
    var counter = 0;
    
    // 返回的函数形成了闭包
    return function() {
        counter++;
        return {
            value: localVar,
            count: counter
        };
    };
}

var myClosure = createClosure();
// myClosure包含了:
// 1. 函数体代码
// 2. 对createClosure作用域的引用(包含localVar和counter)

2. 闭包的形成机制

作用域链和闭包

var globalVar = "global";

function outer() {
    var outerVar = "outer";
    
    function middle() {
        var middleVar = "middle";
        
        function inner() {
            var innerVar = "inner";
            
            // inner函数可以访问所有外部作用域的变量
            console.log(globalVar);  // "global"
            console.log(outerVar);   // "outer"
            console.log(middleVar);  // "middle"
            console.log(innerVar);   // "inner"
        }
        
        return inner;
    }
    
    return middle();
}

var closure = outer();
// closure保持对outer、middle作用域的引用

闭包的生命周期

function createCounter() {
    var count = 0;  // 这个变量不会被回收
    
    // 返回的函数形成闭包,保持对count的引用
    return function() {
        count++;
        return count;
    };
}

var counter1 = createCounter();
var counter2 = createCounter();

console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的闭包实例)

// createCounter函数执行完毕,但count变量由于被闭包引用而不会被回收

3. 闭包的常见应用场景

1. 数据封装和私有变量

// 模块模式 - 创建私有变量
function createBankAccount(initialBalance) {
    var balance = initialBalance;  // 私有变量
    
    return {
        // 公共方法
        deposit: function(amount) {
            if (amount > 0) {
                balance += amount;
                return balance;
            }
        },
        
        withdraw: function(amount) {
            if (amount > 0 && amount <= balance) {
                balance -= amount;
                return balance;
            }
            return "Insufficient funds";
        },
        
        getBalance: function() {
            return balance;  // 通过闭包访问私有变量
        }
    };
}

var account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
account.withdraw(200);
console.log(account.getBalance()); // 1300

// 无法直接访问balance变量
console.log(account.balance); // undefined

2. 函数工厂

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

var double = createMultiplier(2);
var triple = createMultiplier(3);
var square = createMultiplier(4);

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

// 每个函数都保持对不同multiplier值的引用

3. 回调函数和事件处理

// 事件处理中的闭包
function setupEventHandlers() {
    var buttons = document.querySelectorAll('.button');
    
    for (var i = 0; i < buttons.length; i++) {
        // 闭包保持对i的引用
        buttons[i].addEventListener('click', function() {
            console.log('Button ' + i + ' clicked'); // 问题:总是输出最后一个i值
        });
    }
}

// 解决方案1:使用let创建块级作用域
function setupEventHandlersFixed1() {
    var buttons = document.querySelectorAll('.button');
    
    for (let i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener('click', function() {
            console.log('Button ' + i + ' clicked'); // 正确输出
        });
    }
}

// 解决方案2:使用闭包
function setupEventHandlersFixed2() {
    var buttons = document.querySelectorAll('.button');
    
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener('click', (function(index) {
            return function() {
                console.log('Button ' + index + ' clicked');
            };
        })(i));
    }
}

4. 记忆化(Memoization)

// 使用闭包实现函数记忆化
function memoize(fn) {
    var cache = {};  // 缓存结果
    
    return function() {
        var key = JSON.stringify(arguments);
        
        if (key in cache) {
            console.log('Cache hit for:', key);
            return cache[key];
        }
        
        console.log('Computing result for:', key);
        var result = fn.apply(this, arguments);
        cache[key] = result;
        return result;
    };
}

// 斐波那契数列计算
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

var memoizedFibonacci = memoize(fibonacci);

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

5. 柯里化(Currying)

// 使用闭包实现函数柯里化
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        
        return function(...nextArgs) {
            return curried.apply(this, args.concat(nextArgs));
        };
    };
}

// 示例函数
function add(a, b, c) {
    return a + b + c;
}

var curriedAdd = curry(add);

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

4. 闭包的高级应用

1. 迭代器模式

// 使用闭包创建迭代器
function createIterator(array) {
    var index = 0;
    
    return {
        next: function() {
            if (index < array.length) {
                return {
                    value: array[index++],
                    done: false
                };
            }
            
            return {
                done: true
            };
        },
        
        reset: function() {
            index = 0;
        }
    };
}

var iterator = createIterator([1, 2, 3, 4, 5]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }

2. 防抖和节流

// 防抖函数
function debounce(func, delay) {
    var timeoutId;
    
    return function(...args) {
        // 清除之前的定时器
        clearTimeout(timeoutId);
        
        // 设置新的定时器
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// 节流函数
function throttle(func, limit) {
    var inThrottle;
    
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            
            setTimeout(() => {
                inThrottle = false;
            }, limit);
        }
    };
}

// 使用示例
var debouncedSearch = debounce(function(query) {
    console.log('Searching for:', query);
}, 300);

var throttledScroll = throttle(function() {
    console.log('Scrolling...');
}, 100);

3. 配置和状态管理

// 使用闭包管理配置
function createConfigManager(initialConfig) {
    var config = Object.assign({}, initialConfig);
    
    return {
        get: function(key) {
            return config[key];
        },
        
        set: function(key, value) {
            config[key] = value;
        },
        
        getAll: function() {
            return Object.assign({}, config);
        },
        
        reset: function() {
            config = Object.assign({}, initialConfig);
        }
    };
}

var config = createConfigManager({
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3
});

console.log(config.get('apiUrl')); // https://api.example.com
config.set('timeout', 10000);
console.log(config.getAll()); // { apiUrl: ..., timeout: 10000, retries: 3 }

5. 闭包的内存考虑

内存泄漏风险

// 潜在的内存泄漏
function createLeakyClosure() {
    var largeData = new Array(1000000).fill('data');  // 大量数据
    var element = document.getElementById('myElement');
    
    // 闭包保持对largeData和element的引用
    element.addEventListener('click', function() {
        console.log(largeData.length);
    });
    
    // 问题:即使element被移除,largeData也不会被回收
}

// 解决方案:及时清理引用
function createSafeClosure() {
    var largeData = new Array(1000000).fill('data');
    var element = document.getElementById('myElement');
    
    var handler = function() {
        console.log(largeData.length);
    };
    
    element.addEventListener('click', handler);
    
    // 提供清理方法
    return function cleanup() {
        element.removeEventListener('click', handler);
        largeData = null;  // 释放引用
        element = null;
    };
}

性能优化

// 闭包性能优化示例
function createOptimizedCounter() {
    var count = 0;
    
    // 避免在闭包中创建不必要的函数
    function increment() {
        return ++count;
    }
    
    function decrement() {
        return --count;
    }
    
    // 返回对象而不是多个闭包
    return {
        increment: increment,
        decrement: decrement,
        getCount: function() {
            return count;
        }
    };
}

// 对比:创建多个独立闭包(性能较差)
function createMultipleClosures() {
    var count = 0;
    
    return {
        increment: function() {  // 每次都创建新函数
            return ++count;
        },
        decrement: function() {  // 每次都创建新函数
            return --count;
        },
        getCount: function() {   // 每次都创建新函数
            return count;
        }
    };
}

6. 闭包与其他概念的关系

闭包与this绑定

// 闭包中的this问题
var obj = {
    name: 'MyObject',
    
    createMethod: function() {
        var self = this;  // 保存this引用
        
        return function() {
            console.log(self.name);  // 通过闭包访问正确的this
        };
    },
    
    createArrowFunction: function() {
        // 箭头函数自动绑定外层this
        return () => {
            console.log(this.name);
        };
    }
};

var method = obj.createMethod();
var arrowMethod = obj.createArrowFunction();

method();        // "MyObject"
arrowMethod();   // "MyObject"

闭包与模块模式

// 现代模块模式
var MyModule = (function() {
    // 私有变量和函数
    var privateVar = 'private';
    
    function privateFunction() {
        return 'This is private';
    }
    
    // 公共API
    return {
        publicVar: 'public',
        
        publicMethod: function() {
            // 通过闭包访问私有成员
            return privateFunction() + ' and ' + privateVar;
        },
        
        setPrivateVar: function(value) {
            privateVar = value;
        },
        
        getPrivateVar: function() {
            return privateVar;
        }
    };
})();

console.log(MyModule.publicMethod()); // "This is private and private"
MyModule.setPrivateVar('modified');
console.log(MyModule.getPrivateVar()); // "modified"

7. 闭包的最佳实践

最佳实践总结

// 1. 合理使用闭包,避免过度嵌套
// 不好的做法
function badExample() {
    return function() {
        return function() {
            return function() {
                return "Too deep!";
            };
        };
    };
}

// 2. 及时清理不需要的闭包引用
function goodCleanup() {
    var largeData = new Array(1000000);
    
    var process = function() {
        // 处理数据
        return largeData.length;
    };
    
    // 使用完后清理
    var result = process();
    largeData = null;  // 释放引用
    return result;
}

// 3. 使用WeakMap处理DOM相关的闭包
function attachDataToElement() {
    var elementData = new WeakMap();
    
    return {
        setData: function(element, data) {
            elementData.set(element, data);
        },
        
        getData: function(element) {
            return elementData.get(element);
        }
        // WeakMap不会阻止元素被垃圾回收
    };
}

// 4. 避免在循环中创建不必要的闭包
function efficientEventBinding() {
    var buttons = document.querySelectorAll('.button');
    
    // 使用事件委托而不是为每个按钮创建闭包
    document.body.addEventListener('click', function(event) {
        if (event.target.classList.contains('button')) {
            var index = Array.prototype.indexOf.call(buttons, event.target);
            console.log('Button', index, 'clicked');
        }
    });
}

闭包是JavaScript中一个强大而重要的概念,它使得函数式编程、模块化开发和数据封装成为可能。正确理解和使用闭包,能够帮助开发者编写出更加优雅和高效的JavaScript代码。同时也要注意闭包可能带来的内存问题,合理使用才能发挥其最大价值。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐