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()
中,属于微任务队列 。
javascript
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 的创建方式,使用箭头函数包裹 resolve
const 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)
- 定义:将多参数函数转换为一系列单参数函数链式调用的技术,依赖闭包保存中间参数 。
- 示例:
javascript
function add(x) {
return function(y) {
return x + y;
};
}
add(2)(3); // 5
- 应用:参数复用、延迟执行、函数组合 。
5. 工厂函数
- 作用:通过闭包生成具有独立状态的函数实例。例如生成独立的计数器:
javascript
function createCounter() {
let count = 0;
return function() {
return ++count;
};
}
const counter1 = createCounter(); // 独立作用域
- 场景:封装私有变量、实现模块化 。
6. 闭包与异步编程的结合
- 事件回调 :在闭包中使用
async/await
处理异步逻辑,避免全局变量污染 。
javascript
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()
中等待执行。例如:javascriptasync function foo() { const result = await somePromise; // 等价于 somePromise.then(result => {...}) }
这种设计使得异步操作的 状态管理 和 错误传播 能够通过 Promise 链式结构实现。
-
Generator 的流程控制
Generator 函数通过
yield
关键字暂停执行,并通过迭代器(Iterator)手动恢复。async/await 利用这一特性,将异步代码的 暂停-恢复机制 转化为生成器函数的yield
操作。例如,以下代码:javascriptasync function a() { const res = await asyncTask(); }
会被 Babel 转换为使用 Generator 的代码:
javascriptfunction a() { return __awaiter(this, void 0, void 0, function* () { const res = yield asyncTask(); }); }
这里的
yield
替代了await
,而__awaiter
函数负责管理生成器的迭代。
二、转换逻辑:代码降级与执行器封装
通过 Babel/TypeScript 等工具的代码转换,async/await 的实现可拆解为以下步骤:
-
生成器函数包装
async 函数被转换为生成器函数,
await
被替换为yield
。例如:javascript// 原始代码 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)
抛出异常。
javascriptfunction __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 解决后加入微任务队列。例如:javascriptasync 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()
链。例如:javascriptasync function foo() { try { await somePromise(); } catch (err) { handleError(err); } }
转换后逻辑:
javascriptfunction* foo() { try { const result = yield somePromise(); } catch (err) { handleError(err); } }
执行器在生成器抛出错误时触发
reject
。 -
未捕获异常的传播
若未使用
try/catch
,错误会通过 Promise 链冒泡到顶层,触发unhandledrejection
事件。
五、性能与优化考量
-
生成器与 Promise 的开销
async/await 相比原生 Promise 链会引入额外开销(如生成器对象的创建),但在现代引擎中差异可忽略。
-
并发的实现方式
需显式使用
Promise.all()
实现并行,避免顺序等待:javascriptasync 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 的完全兼容性。