JavaScript 基础回顾:作用域、闭包与异步
JS 是前端的地基,但作用域、闭包、异步这三块一直是我的模糊地带。这次专门花时间逐个击破,用例子验证理解。
一、作用域与作用域链
ES6 之后记住一句话就够了:let/const 是块级作用域,var 是函数作用域且有提升。新项目直接用 const 优先、let 兜底、忘掉 var。
变量查找沿作用域链由内向外进行,内层能访问外层,外层访问不到内层。
二、闭包:函数记住了它的出生地
闭包 = 函数 + 它能访问的外层词法环境。即使外层函数已经执行完,内层函数依然能访问当时的变量。
function makeCounter() {
let count = 0; // 被闭包"记住"的变量
return function () {
return ++count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2 count 没有被销毁!
闭包的实际用途:数据私有化、函数工厂、回调里保存现场。经典面试题"循环里的 var 打印同一个数",本质就是闭包共享了同一个变量,用 let 即可解决。
三、异步与事件循环
JS 单线程,异步任务通过事件循环调度。执行顺序规则:
- 先执行完所有同步代码;
- 每轮先清空微任务队列(Promise.then、queueMicrotask);
- 再取一个宏任务执行(setTimeout、setInterval、I/O);
- 如此往复。
console.log('1 同步');
setTimeout(() => console.log('4 宏任务'), 0);
Promise.resolve().then(() => console.log('3 微任务'));
console.log('2 同步');
// 输出: 1 同步 → 2 同步 → 3 微任务 → 4 宏任务
口诀:同步优先,微任务插队,宏任务排队。就算 setTimeout 延时是 0,也得等微任务清空。
四、async/await 是语法糖
async/await 本质上是 Promise 的写法优化,让异步代码看起来像同步:
async function loadData() {
try {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
} catch (err) {
console.error('加载失败', err);
}
}
注意 await 会暂停当前 async 函数,但不会阻塞主线程——函数外的代码照常执行。
五、小结
这三个概念其实是同一条线:作用域决定变量可见性 → 闭包利用作用域链延长变量生命 → 事件循环决定代码什么时候执行。串起来理解之后,以前觉得玄学的 bug 都能讲出道理了。