闭包讲解
ES6回顾:闭包->(优点:实现工厂函数、记忆化和异步实现)、(应用场景:Promise的then与catch的回调、async/await、柯里化函数)
以下是与 JavaScript 闭包相关的常见考点整理,结合 Promise、async/await、缓存结果、工厂函数、柯里化等内容,并依据我搜索到的资料进行详细说明:
1. 闭包(Closure)
- 定义:由函数及其引用的外部词法环境变量组成,即使外部函数执行完毕,内部函数仍能访问这些变量 。
- 作用:
- 延长外部函数变量的生命周期,使外部可操作内部数据(如模块化封装)。
- 避免全局变量污染,实现私有变量 。
- 缺点:不当使用会导致内存泄漏(变量无法被回收)。
- 应用场景:
- 缓存结果:通过闭包保存计算结果,避免重复计算(记忆化函数)。
- 工厂函数:生成带有特定配置的函数实例(如计数器生成器)。
- 柯里化:拆分多参数函数为链式单参数调用,依赖闭包保存中间参数 。
- 异步回调:在事件处理或定时器中保留上下文 。
2. Promise
- 核心概念:
- 三种状态:
pending
、fulfilled
、rejected
,状态一旦改变不可逆 。 - 解决回调地狱(Callback Hell),支持链式调用
.then()
和.catch()
。
- 三种状态:
- 常用方法:
Promise.all()
:所有 Promise 成功时返回结果数组,任一失败立即拒绝 。Promise.race()
:首个完成的 Promise 决定最终状态 。Promise.resolve()
/Promise.reject()
:快速创建成功/失败的 Promise 。
- 手写实现:常考手写
Promise.all
和Promise.race
的实现 。
3. async/await
- 本质:基于 Generator 和 Promise 的语法糖,使异步代码更接近同步写法 。
- 规则:
async
函数返回 Promise 对象,return
值会被Promise.resolve()
包装 。await
后接 Promise,暂停当前函数执行直到 Promise 完成(属于微任务)。await
只能在async
函数内使用,否则需通过立即调用异步函数(如(async () => { ... })()
)。
- 错误处理:用
try...catch
捕获await
后的 Promise 拒绝 。 - 执行顺序:
await
后的代码相当于放入.then()
中,属于微任务队列 。
function asyncToGenerator(fn) {// 返回函数return function() {const gen = fn.apply(this, arguments);//生成器// 方法返回一个Promise对象return new Promise((resolve, reject) => {function step(key, arg) {// console.log(arg)let result;try {result = gen[key](arg);//生成器返回数据{value,done:bool}// 移除调试日志以保持输出整洁} catch (error) {return reject(error);}//如果执行器执行完成if (result.done) {return resolve(result.value);}//如果执行器未完成,将当前结果传入Promise的resolve方法中,并递归调用step方法Promise.resolve(result.value).then(v => step("next", v),e => step("throw", e));}step("next");});};
}// 使用示例(已修复)
const asyncFunction = asyncToGenerator(function* () {// 修正 Promise 的创建方式,使用箭头函数包裹 resolveconst result = yield new Promise(resolve => setTimeout(() => resolve("result"), 1000));const result1 = yield new Promise(resolve => setTimeout(() => resolve("result2"), 2000));console.log(result, result1); // 输出: "result" "result2"(3秒后)
});asyncFunction(); // 启动执行
4. 柯里化(Currying)
- 定义:将多参数函数转换为一系列单参数函数链式调用的技术,依赖闭包保存中间参数 。
- 示例:
function add(x) {return function(y) {return x + y;};}add(2)(3); // 5
- 应用:参数复用、延迟执行、函数组合 。
5. 工厂函数
- 作用:通过闭包生成具有独立状态的函数实例。例如生成独立的计数器:
function createCounter() {let count = 0;return function() {return ++count;};}const counter1 = createCounter(); // 独立作用域
- 场景:封装私有变量、实现模块化 。
6. 闭包与异步编程的结合
- 事件回调:在闭包中使用
async/await
处理异步逻辑,避免全局变量污染 。
button.addEventListener('click', () => {(async () => {const data = await fetchData();console.log(data);})();});
- 定时器:闭包保存定时器状态,结合
async/await
控制执行流程 。
7. 内存管理
- 内存泄漏:闭包长期引用外部变量会导致内存无法释放,需及时解除引用(如手动置
null
)。 - 优化:避免不必要的闭包,或在不需要时清除事件监听器、定时器等 。
总结
闭包是 JavaScript 的核心概念,与异步编程(Promise/async/await)、函数式编程(柯里化、工厂函数)紧密相关。理解闭包的作用域机制、内存管理,以及与其他特性的结合方式,是应对面试和实际开发的关键。
async/await 底层实现解析
async/await 是 JavaScript 中处理异步操作的语法糖,其底层实现基于 Promise 和 Generator(生成器) 的协同机制。通过分析资料中的代码转换、设计原理和规范定义,其核心实现逻辑可拆解如下:
一、核心依赖:Promise 与 Generator 的协作
-
Promise 的基础作用
async/await 的异步控制完全依赖于 Promise 的链式调用。每个await
表达式本质上会创建一个 Promise,并将后续代码包装到.then()
中等待执行。例如:async function foo() {const result = await somePromise; // 等价于 somePromise.then(result => {...}) }
这种设计使得异步操作的 状态管理 和 错误传播 能够通过 Promise 链式结构实现。
-
Generator 的流程控制
Generator 函数通过yield
关键字暂停执行,并通过迭代器(Iterator)手动恢复。async/await 利用这一特性,将异步代码的 暂停-恢复机制 转化为生成器函数的yield
操作。例如,以下代码:async function a() {const res = await asyncTask(); }
会被 Babel 转换为使用 Generator 的代码:
function a() {return __awaiter(this, void 0, void 0, function* () {const res = yield asyncTask();}); }
这里的
yield
替代了await
,而__awaiter
函数负责管理生成器的迭代。
二、转换逻辑:代码降级与执行器封装
通过 Babel/TypeScript 等工具的代码转换,async/await 的实现可拆解为以下步骤:
-
生成器函数包装
async 函数被转换为生成器函数,await
被替换为yield
。例如:// 原始代码 async function fetchData() {const data = await fetch(url);return data; }// 转换后代码(简化) function fetchData() {return __awaiter(this, function* () {const data = yield fetch(url);return data;}); }
-
执行器函数(如
__awaiter
)的作用
执行器负责驱动生成器的迭代,并处理 Promise 的链式调用。其核心逻辑如下:- 将生成器的每个
yield
值包装为 Promise。 - 通过
generator.next(value)
将 Promise 的结果传递回生成器。 - 捕获错误并通过
generator.throw(error)
抛出异常。
function __awaiter(generator) {return new Promise((resolve, reject) => {function step(result) {if (result.done) {resolve(result.value);} else {Promise.resolve(result.value).then(value => step(generator.next(value)), // 传递结果并继续迭代error => step(generator.throw(error)) // 抛出错误);}}step(generator.next());}); }
此过程实现了 自动迭代 和 错误冒泡,使代码看似同步执行。
- 将生成器的每个
三、执行顺序与事件循环的关联
-
微任务队列的调度
await
后的代码会被包装为微任务(Microtask),在 Promise 解决后加入微任务队列。例如:async function demo() {console.log(1);await Promise.resolve();console.log(2); // 相当于 Promise.resolve().then(() => console.log(2)) } demo(); console.log(3); // 输出顺序:1 → 3 → 2
这种机制确保了异步代码的执行不会阻塞主线程。
-
协程(Coroutine)模型的实现
async/await 通过生成器和 Promise 模拟了协程的 挂起-恢复 行为:- 挂起:在
await
处暂停生成器,释放主线程。 - 恢复:当 Promise 解决后,通过执行器继续生成器的迭代。
- 挂起:在
四、错误处理机制的实现
-
try/catch 的转换
async 函数中的try/catch
会被转换为 Promise 的.catch()
链。例如:async function foo() {try {await somePromise();} catch (err) {handleError(err);} }
转换后逻辑:
function* foo() {try {const result = yield somePromise();} catch (err) {handleError(err);} }
执行器在生成器抛出错误时触发
reject
。 -
未捕获异常的传播
若未使用try/catch
,错误会通过 Promise 链冒泡到顶层,触发unhandledrejection
事件。
五、性能与优化考量
-
生成器与 Promise 的开销
async/await 相比原生 Promise 链会引入额外开销(如生成器对象的创建),但在现代引擎中差异可忽略。 -
并发的实现方式
需显式使用Promise.all()
实现并行,避免顺序等待:async function parallel() {const [a, b] = await Promise.all([task1(), task2()]); // 并行执行 }
若直接顺序
await
,会导致任务串行执行。
总结:async/await 的架构设计
层级 | 实现机制 | 作用 |
---|---|---|
语法层 | async /await 关键字 | 提供同步代码风格的异步写法 |
转换层 | Babel/TypeScript 代码降级 | 将 async/await 转为 Generator + Promise |
运行时层 | 生成器迭代器 + Promise 链 | 管理暂停/恢复、错误传播 |
事件循环层 | 微任务队列调度 | 确保异步代码非阻塞执行 |
通过多层抽象,async/await 将复杂的异步流程控制简化为直观的同步式代码,同时保持与 Promise 的完全兼容性。