闭包
如何理解闭包
闭包(Closure)是 JavaScript 中的一个重要概念,也是很多前端面试和开发中经常遇到的问题。理解闭包的关键在于理解 函数作用域 和 作用域链。
闭包的定义
闭包是函数和其能够访问的变量环境(词法环境 lexical environment)的组合。
更直白一点:
闭包就是一个函数能够“记住”并访问它定义时的作用域,即使这个函数在其定义作用域之外被调用。
也可以简单理解为,以函数作为参数或返回值的函数,在函数执行时,会生成一个闭包,闭包中包含了函数定义时的作用域。
示例
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const fn = outer(); // outer() 执行,返回 inner 函数
fn(); // 输出 1
fn(); // 输出 2
outer
是外部函数,它内部定义了一个局部变量count
和一个内部函数inner
outer()
返回了inner
函数fn
持有对inner
函数的引用,并且通过这个函数 访问了outer
中的局部变量count
- 即使
outer()
已经执行完了,它的局部变量count
并没有被销毁,这是因为inner
函数形成了闭包,持有对count
的引用
// 以函数作为参数的例子
function createFilterProcessor(filterFn) {
return function (arr) {
return arr.filter(filterFn); // 使用闭包捕获 filterFn
};
}
// 定义一个过滤函数:只保留偶数
function filterFn(num) {
return num % 2 === 0;
}
// 使用闭包创建一个特定的处理器
const evenFilter = createFilterProcessor(filterFn);
const numbers = [1, 2, 3, 4, 5, 6];
const result = evenFilter(numbers); // [2, 4, 6]
console.log(result);
evenFilter
记住了 filterFn
函数,即使 createFilterProcessor
函数已经执行完了,evenFilter
函数仍然可以访问 filterFn
函数。filterFn
函数不会被垃圾回收机制回收。
闭包的作用
封装私有变量
外部无法直接访问 count
,只能通过提供的 increment
和 decrement
操作变量,实现了私有化。
function createCounter() {
let count = 0;
return {
increment() {
count++;
console.log(count);
},
decrement() {
count--;
console.log(count);
},
getCount() {
return count;
},
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1
实现函数工厂
函数工厂,根据参数生成不同的函数。
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
console.log(add5(10)); // 15
实现函数缓存
// 函数缓存, 缓存函数的结果,避免重复计算
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
柯里化
function add(a) {
return function (b) {
return a + b;
};
}
console.log(add(2)(3)); // 5
函数柯里化的作用
函数柯里化(Currying)是把接收多个参数的函数,变换成接收一个参数的函数,并且返回一个新的函数的过程。通俗点说,就是把 f(a, b, c) 这种函数,转化成 f(a)(b)(c) 的形式。
参数复用
你可以先固定一部分参数,得到一个新的函数,等以后再传剩下的参数。
function add(a) {
return function (b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(10)); // 15
有时候我们需要绑定事件,但事件处理函数里要带上额外参数。
// handleClick('primary') 就是柯里化后的函数,提前固定了 type
function handleClick(type) {
return function (event) {
console.log(`按钮类型: ${type}, 点击事件:`, event);
};
}
button1.addEventListener("click", handleClick("primary"));
button2.addEventListener("click", handleClick("danger"));
延迟执行
通过柯里化,可以在收集完参数之前不执行函数,等参数齐了再执行。适合场景:事件绑定、链式调用等。
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function (...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
常见的表单校验函数,可以用柯里化实现复用
function isLengthBetween(min) {
return function (max) {
return function (value) {
return value.length >= min && value.length <= max;
};
};
}
const isUsernameValid = isLengthBetween(3)(10);
console.log(isUsernameValid("Tom")); // true
console.log(isUsernameValid("T")); // false
闭包和柯里化的区别
闭包和柯里化是两个不同的概念,但它们之间有很强的关联性。
闭包是函数和其能够访问的变量环境(词法环境 lexical environment)的组合。它的目的主要是保存状态,隔离作用域,让函数能够记住外部变量,形成私有变量。
柯里化是函数式编程中的一个概念,它把接收多个参数的函数,变换成接收一个参数的函数,并且返回一个新的函数的过程。它的目的主要是参数复用,延迟执行,让函数能够更灵活地组合。
联系
柯里化一定依赖闭包。因为柯里化要记住前面传入的参数,这个记忆过程就是靠闭包实现的。
闭包不一定是柯里化。闭包更宽泛,除了柯里化,它还能做模块封装、模拟私有变量、缓存等。
柯里化是用闭包实现的一种应用模式。