JavaScript高级特性剖析:闭包
JavaScript高级特性剖析:闭包
- 一,闭包的概念
- 前言
- 1,核心定义
- 2,关键特性
- 二,闭包的原理
- 三,闭包的用法
- 四,闭包的注意事项
- 五,总结
一,闭包的概念
前言
闭包(Closure)是 JavaScript 的一个核心特性,它是 JavaScript 语言设计的一部分,从 JavaScript 诞生之初就存在。闭包的概念并不是 JavaScript 独有的,但它在 JavaScript 中表现得尤为突出和重要。
在js中因为闭包特性的存在,这些功能才得以实现:
- 数据封装:通过闭包可以实现私有变量和方法。
- 回调函数:闭包使得回调函数可以访问定义时的上下文。
- 函数柯里化:闭包是实现函数柯里化(Currying)的基础。
- 模块化:闭包是 JavaScript 模块化的基础。
1,核心定义
- 闭包是函数和其创建时的词法环境(lexical environment) 的组合。简单来说:
当一个函数可以记住并访问它定义时的作用域,即使这个函数在其父作用域之外执行,就形成了闭包。 - 闭包允许函数“捕获”它被创建时的上下文中的变量。
2,关键特性
- 跨作用域访问:当一个函数(内部函数)能访问其声明时的作用域链中的变量,即使这个函数在其父作用域之外执行。
- 环境保留:闭包会保留对其词法环境的引用,使得这些变量不会被垃圾回收机制回收。
二,闭包的原理
当了解了特性后,我们继续探讨其原理:
- JavaScript 的作用域是静态作用域,函数的作用域在定义时就已经被确定,而不是在执行时。
- 当函数内部定义另一个函数时,内部函数会持有外部函数的作用域引用。
- 即使外部函数执行完毕,如果内部函数仍然存在(例如被返回、保存或作为回调),外部函数的变量不会被垃圾回收,因为内部函数可能还会访问它们。
三,闭包的用法
- 示例 1:最简单的闭包
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 输出 1
counter(); // 输出 2
分析:inner 函数记住了它定义时的环境(count 变量)每次调用 counter() 时,count 的值会被保留下来。
- 示例 2:私有变量封装
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() { count++; },
getCount: function() { return count; },
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
console.log(counter.count); // 输出 undefined(无法直接访问)
分析:count 被封装在闭包中,只能通过 increment 和 getCount 方法操作。
- 示例 3:回调函数和事件处理
function delayMessage(message, delay) {
setTimeout(function() {
console.log(message);
}, delay);
}
delayMessage("delay 2s", 2000);
分析:闭包保留了 message 的引用
- 示例 4:柯里化函数
// 普通函数
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 输出 6
// 柯里化后的函数(分步传参)
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 调用方式
const step1 = addCurried(1); // 返回一个函数,记住 a=1
const step2 = step1(2); // 返回一个函数,记住 b=2
const result = step2(3); // 最终计算 1+2+3=6
console.log(result); // 输出 6
// 也可以链式调用
console.log(addCurried(1)(2)(3)); // 输出 6
函数柯里化是一种将多参数函数转换为单参数函数链的技术。
具体来说,柯里化后的函数会逐步接收参数,每次接收一个参数,并返回一个新的函数来接收下一个参数,直到所有参数收集完毕,最终返回结果。
可以看到在js中柯里化就是利用了js的闭包特性
柯里化的核心思想
分步传递参数:将一次性传递多个参数的方式,改为分步传递。
延迟执行:柯里化后的函数可以延迟到所有参数收集完毕后再执行。
复用性:可以复用部分参数,生成更具体的函数。
柯里化函数具体应用场景举例:
- 记录日志
function createLogger(prefix) {
// 通过闭包记住
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
const infoLogger = createLogger("INFO");
infoLogger("INFO LOG OK"); // [INFO] INFO LOG OK
const errorLogger = createLogger("ERROR");
errorLogger("ERROR LOG OK"); // [ERROR] ERROR LOG OK
- 范围判断
// 柯里化校验函数
function validate(min) {
return function(max) {
return function(value) {
return value >= min && value <= max;
};
};
}
// 校验值是否在 13~60 之间
const validateAge = validate(13)(60);
console.log(validateAge(13)); // true
console.log(validateAge(111)); // false
- 示例 5:模块化开发
const module = (function() {
let privateVar = 10;
function privateMethod() {
return privateVar * 2;
}
return {
publicMethod: function() {
return privateMethod();
}
};
})();
console.log(module.publicMethod()); // 输出 20
分析:这段代码通过 立即执行函数(IIFE) 和 闭包实现了模块化。
私有变量和函数被封装在模块内部,外部无法直接访问。
公有方法作为接口,通过闭包间接操作私有成员。
四,闭包的注意事项
从闭包的特性和底层原理:“外部函数执行完毕,如果内部函数仍然存在(例如被返回、保存或作为回调),外部函数的变量不会被垃圾回收。”来看,使用闭包特性需要避免内存泄漏风险,如果变量不再需要,切记手动解除引用:
function buildLargeArray() {
let bigData = new Array(1000000).fill("*"); // 大数组
return {
process: function() {
console.log("process",bigData);
},
clear: function() {
bigData = null; // 手动解除引用
console.log("clear",bigData);
},
};
}
const processor = buildLargeArray();
processor.process(); // 执行任务
processor.clear(); // 手动解除引用
五,总结
一句话:
闭包就是让函数记住自己出生的地方,使用闭包要注意内存泄露问题。