JavaScript 面试题:5 道分主题详解

五道题,覆盖前端面试反复回到的四个方面。每组先给出能答掉整族问题的那条规则,再给你可以逐行推演的代码。

JavaScript 面试反复用同一小撮题,因为少数几条规则能解释几乎所有反直觉输出:作用域与提升、this 在调用时如何绑定、事件循环「微任务先于宏任务」的顺序、以及 == 下的类型转换。

价值不在背结果,而在于每条规则都能把一整族问题压缩成一句话。一旦你能说出「let 每轮迭代创建新绑定」,循环题、IIFE 追问和闭包题就都是同一个答案。

先自己推每段代码再看解析。每条解析都点出通常紧跟着来的追问——第一个正确答案只换来更难的那一版。

四个方面,五道题

var、let 与循环闭包这道经典题

1 道题

老实答案就一个词——作用域——但面试官要的是后果。var 是函数作用域且会被提升(初始化为 undefined),let 是块作用域、在声明执行前处于暂时性死区。这个差别正是「循环里 setTimeout」那道题会因关键字不同而给出两种答案的全部原因,而这道题几乎每场前端面试都问。

通常怎么问「var 和 let 有什么区别?」·「为什么这个循环打印 3, 3, 3?」

第 1 题

两个循环分别打印什么?

for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j));
  1. A先 0 1 2,再 0 1 2
  2. B先 3 3 3,再 0 1 2
  3. C先 0 1 2,再 3 3 3
  4. D先 3 3 3,再 3 3 3
看答案与解析

正确答案B. 先 3 3 3,再 0 1 2

🐱 var i 是三个回调共享的同一个绑定;定时器触发时循环早已结束,i 是 3。let j 每轮迭代创建一个新绑定,所以每个回调闭包住自己那份值。紧跟着的追问通常是:不换关键字怎么修 var 版本——每轮套一个 IIFE,(function(i){ setTimeout(() => console.log(i)); })(i),这正是 ES6 之前用来替代的老写法。

this:四条绑定规则

1 道题

this 在调用时由怎么调用决定,而不是在哪里定义——只有一个例外,而正是这个例外让整个话题变得可答。按优先级:newthis 绑到新对象;显式 call/apply/bind 绑你传进去的;方法调用绑点号前面那个对象;否则严格模式下是 undefined(非严格是全局对象)。例外是:箭头函数没有自己的 this,按词法继承外层——这就是它终结了 const self = this 那个年代的原因。

通常怎么问「这里的 this 是什么?」·「箭头函数为什么不一样?」

第 2 题

这段代码打印什么?

const obj = {
  name: 'cat',
  regular() { return this.name; },
  arrow: () => this?.name,
};
console.log(obj.regular(), obj.arrow());
  1. A'cat' 'cat'
  2. B'cat' undefined
  3. Cundefined 'cat'
  4. D抛出 TypeError
看答案与解析

正确答案B. 'cat' undefined

🐱 regular 以方法形式调用,this 就是 obj,返回 'cat'。arrow 定义在外层作用域里——对象字面量不创建作用域——所以它的 this 沿用外层(模块作用域下是 undefined),得到 undefined。可以直接说出的规则:需要 this 的方法绝不要用箭头函数;方法内部的回调则应该用箭头函数,那正是词法绑定帮上忙的地方。

事件循环:执行顺序为什么反直觉

1 道题

一条规则能答掉大部分这类题:同步代码先跑完,然后清空所有待处理的微任务(promise 回调、queueMicrotask),再执行一个宏任务(setTimeout、I/O)——而且每个宏任务之后微任务队列会再被清空一遍。所以同一时刻安排的 .then 永远先于 setTimeout(…, 0),无论代码在页面上的先后。

通常怎么问「这段代码按什么顺序打印?」·「微任务和宏任务有什么区别?」

第 3 题

输出顺序是什么?

console.log('A');
setTimeout(() => console.log('B'));
Promise.resolve().then(() => console.log('C'));
console.log('D');
  1. AA B C D
  2. BA D C B
  3. CA D B C
  4. DA C D B
看答案与解析

正确答案B. A D C B

🐱 先同步:A、D。再清微任务队列:C。最后宏任务:B。A D C B 这个答案是经典演示——promise 不是「更快的定时器」,它跑在另一条队列上,而那条队列在任何定时器轮到之前就被清空了。追问通常是 async/awaitawait 会把函数切开、以微任务恢复,所以在顺序上等同于 .then

== 与 === 以及类型转换那些题

1 道题

=== 比较类型和值、不做转换;== 会先转换,规则复杂到几乎没人能背对。职业的答法不是背转换表,而是给出策略:一律用 ===,唯一惯用的例外是 x == null,它能一次判掉 null 和 undefined。问 [] == false 的面试官,通常是在看你懂不懂避开这种局面,而不是能不能照着规范推。

通常怎么问「== 和 === 有什么区别?」·「为什么 [] == false 是 true?」

第 4 题

在现代 JavaScript 里,== 唯一被认可的惯用写法是哪个?

  1. Ax == 0,顺便把空字符串也判进来
  2. Bx == null,它对 null 和 undefined 都为真
  3. Cx == false,用来测试假值
  4. D都不是;== 已废弃
看答案与解析

正确答案B. x == null,它对 null 和 undefined 都为真

🐱 x == null 是被认可的例外,因为它简洁地覆盖 null 与 undefined、且覆盖这两个——很多代码规范恰恰只放行这一条。其余都是坑:'' == 0 为真、[] == false 为真,依赖它们produce 出的 bug 能骗过代码评审,因为看起来像是故意写的。== 并未废弃,只是被避开。

继续练

JavaScript 面试题 — 常见问题

JavaScript 面试最常问哪些题?

var 与 let(以及 setTimeout 循环题)、this 如何绑定、事件循环的微任务/宏任务顺序、== 与 ===、闭包、原型继承。前四项在本页;闭包并入作用域那一节。

事件循环需要掌握到多细?

够你预测输出即可:同步代码 → 清空微任务 → 一个宏任务,如此循环。能叫出队列名字并正确预测顺序,在资深以下的岗位基本就是全部要求。

该说「永远不用 ==」吗?

该说「一律用 ===,唯一例外是 x == null」。这听起来是你有策略,而不是背了张转换表——后者才是这道题真正在探的东西。

这些是真实面试题吗?

它们是围绕面试反复考查的概念编写的原创题,不是任何公司面试流程的实录。