ZxdNoob
首页文章简历版本历史AI 向导

ZxdNoob

用心记录,认真生活

按 ⌘K 快速导航到任意页面按 ⌘I 打开 AI 向导(或访问 /agent)

导航

  • 首页
  • 简历
  • 版本历史
  • AI 向导

更多

  • GitHub
  • Sitemap

© 2026 ZxdNoob. All rights reserved.

Built with Next.js · Styled with Tailwind CSS

返回文章列表
2026年4月1日 12:30:007 分钟阅读

前端面试准备 01:JavaScript 运行时——执行上下文、闭包、原型链与事件循环(能推导版)

把 JS 面试高频点组织成“可推导”的模型:执行上下文/作用域链/this/原型链/事件循环。附典型追问、手写题拆解与易错边界。

  • 前端面试
  • JavaScript
  • 运行时
  • 异步
AI 共读让 Noob 帮你提炼要点 / 答疑

这篇只做一件事:把 JS 高频题从“背答案”变成“能推导”。

你要建立的不是零散知识点,而是一套“运行时模型”:

  • 执行:代码如何进入调用栈、如何创建执行上下文
  • 绑定:this 如何确定、原型链如何查找属性
  • 调度:异步任务如何排队、何时执行(事件循环)

当你能画出这三张图(栈 / 原型链 / 队列),大多数变形题都不怕。

01 执行上下文:你要能画出栈#

面试问“闭包是什么/this 指向是什么”,本质都离不开执行上下文。

你至少要讲清:

  • JS 执行时维护一个调用栈(Call Stack)
  • 每次函数调用会创建一个执行上下文:变量环境/词法环境/this 绑定
  • 作用域解析遵循词法作用域(代码写在哪,就在哪找变量)

高频追问#

  • var/let/const 的差异为何会导致 TDZ?
  • 函数声明提升 vs 函数表达式提升的本质差别?

02 闭包:不是“函数套函数”,是“携带了外部环境”#

闭包的面试表达建议:

闭包是“函数 + 它创建时的词法环境”的组合。函数返回后,其外部变量仍可能被引用而不被回收。

你要能讲清两个点:

  • 为什么外部变量不会销毁:因为仍被引用(可达性)
  • 代价是什么:更长的生命周期,可能引发内存占用与泄漏

实战落地#

  • 订阅/事件监听务必在卸载时解除
  • 定时器/异步回调引用大对象要小心

03 this:把绑定规则背成一张表#

你不需要讲玄学,只要把规则说清:

  • 默认绑定:严格模式下为 undefined,否则为全局对象
  • 隐式绑定:obj.fn() -> this = obj(但会被“引用丢失”打断)
  • 显式绑定:call/apply/bind
  • new 绑定:构造调用优先级最高

易错点:

  • 箭头函数没有自己的 this,捕获定义时外层 this

04 原型链:属性查找与继承的真实路径#

必会表达:

  • 每个对象都有 [[Prototype]](可通过 Object.getPrototypeOf 观察)
  • 函数有 prototype 属性,用于 new 时设置实例的原型
  • 属性查找沿原型链向上,直到 null

高频追问:

  • instanceof 原理是什么?
  • 如何实现 Object.create?

05 事件循环:宏任务/微任务不是背分类,是理解“何时入队”#

你要能讲清:

  • 执行栈清空后,取一个宏任务执行
  • 宏任务执行过程中产生的微任务会在本轮宏任务结束后全部清空
  • DOM 渲染/绘制时机与任务队列之间的关系(不同浏览器实现细节可简述)

建议你能手写并解释:

  • Promise.then 的回调为什么比 setTimeout 更早执行
  • async/await 如何拆成 Promise 链

06 自测清单(60 秒版本)#

  • 我能画出调用栈解释闭包产生与释放吗?
  • 我能用绑定规则解释 3 个 this 变形题吗?
  • 我能说明原型链查找过程与 instanceof 原理吗?
  • 我能口述事件循环执行顺序并解释原因吗?

下一篇我们进入 TypeScript:把“会写 TS”升级为“会用类型系统提升工程质量”。

07 深入:把 4 个核心模型讲清(面试可用表述)#

A. 调用栈(Call Stack)#

  • JS 单线程执行:同一时刻只有一个栈顶函数在跑
  • 栈溢出通常来自递归或同步死循环

B. 作用域与词法环境(Lexical Environment)#

  • 作用域由“代码位置”决定,不由“调用位置”决定(词法作用域)
  • 闭包是函数携带词法环境导致外部变量延长生命周期

C. 原型链(Prototype Chain)#

  • “查属性”走原型链,“改属性”通常在当前对象上新增/覆盖
  • Object.create(null) 没有原型链(没有 toString 等)

D. 任务队列(Task Queues)#

  • 宏任务:脚本、定时器、I/O、事件回调(分类会因环境不同略有差异)
  • 微任务:Promise reaction、queueMicrotask(优先级更高)

08 笔试题(选择 / 问答 / 输出)#

题 1(输出题):变量提升与 TDZ#

下面代码输出什么?为什么?

console.log(a);
var a = 1;

try {
  console.log(b);
} catch (e) {
  console.log('err');
}
let b = 2;

参考答案(原理)#

最终输出:

  • 第一段:undefined
  • 第二段:err

下面按“创建阶段(声明处理)→ 执行阶段”推导。

第一段为什么是 undefined

代码:

console.log(a);
var a = 1;

推导步骤:

  1. 进入脚本/函数作用域时会有“创建阶段”。var a 会被加入到当前执行上下文的 Variable Environment。
  2. var 的语义是:声明提升 + 立即初始化为 undefined(所以声明前可读)。
  3. 执行到 console.log(a) 时,a 已存在且值为 undefined,因此输出 undefined。
  4. 下一行 a = 1 才发生赋值,改变 a 的值。
第二段为什么会抛错并被 catch 到

代码:

try {
  console.log(b);
} catch (e) {
  console.log('err');
}
let b = 2;

推导步骤:

  1. let b 同样会在创建阶段被登记到当前执行上下文的 Lexical Environment。
  2. 但 let/const 的语义是:声明会提升,但不会初始化。在执行到声明语句前,变量处于 TDZ(暂时性死区),不可读。
  3. 执行到 console.log(b) 时读取 TDZ 变量,会抛出 ReferenceError。
  4. 错误被 catch 捕获,输出 err。
  5. 执行到 let b = 2 时,b 才完成初始化并变为可读。
易错点与加分点
  • TDZ 不是“没声明”,而是“已声明但不可访问”。
  • const 同样有 TDZ,且必须初始化,否则是语法错误。
  • 在 ES Module 顶层,var 不会挂到全局对象上,但“提升 + 初始化为 undefined”的行为仍成立。

题 2(输出题):this 绑定与引用丢失#

const obj = {
  x: 1,
  f() {
    return this.x;
  },
};
const g = obj.f;
console.log(obj.f(), g());

参考答案(原理)#

这题考点是:this 不是由“函数定义位置/归属对象”决定,而是由**调用点(call-site)**决定。

代码:

const obj = {
  x: 1,
  f() {
    return this.x;
  },
};
const g = obj.f;
console.log(obj.f(), g());
结论
  • obj.f():输出 1。
  • g():
    • 严格模式/模块模式:抛 TypeError(this === undefined,读取 this.x 报错)
    • 非严格模式:通常输出 undefined(this === globalThis,但全局一般没有 x)
推导(逐步)
  1. obj.f()
  • 调用点是“点调用”(obj.f())。
  • 隐式绑定规则:点左侧对象作为 this。
  • 所以 this === obj,this.x === 1。
  1. g()
  • const g = obj.f 只是把函数值赋给变量 g,并不会携带 obj。
  • 调用点变成普通函数调用(g())。
  • 普通调用下:
    • 严格模式:this === undefined
    • 非严格模式:this === globalThis
常见追问(加分)
  • 如何修复引用丢失:obj.f.bind(obj) 或 () => obj.f()
  • 为什么箭头函数不适合写需要动态 this 的方法:箭头函数 this 固定为定义时外层 this。

面试表达要点:隐式绑定需要“调用点”有宿主对象,不是“函数来自哪个对象”。

题 3(输出题):事件循环#

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);

参考答案(原理)#

代码:

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
结论

输出顺序:1 4 3 2

推导(事件循环)

把整段脚本当作一次“宏任务”执行:

  1. 同步执行阶段(仍在当前宏任务内)
  • 输出 1
  • setTimeout 注册回调:进入“未来的宏任务队列”(到期后才可执行)
  • Promise.then 回调:进入微任务队列
  • 输出 4
  1. 当前宏任务结束后:清空微任务队列
  • 执行 then 回调,输出 3
  1. 进入下一轮宏任务
  • 执行定时器回调,输出 2
易错点与边界
  • setTimeout(fn, 0) 并不保证“0ms 后立刻执行”,它只是“尽快排到下一轮宏任务”,还会受最小延迟与浏览器节流影响。
  • Node.js 事件循环阶段更细,但 Promise microtask 依旧会在进入下一轮宏任务前被清空。

09 编码题(手写实现 + 推导)#

题 4:实现 myBind#

实现一个简化版的 bind,支持:

  • 绑定 this
  • 预置参数(partial application)
  • 使用 new 调用时,this 绑定应被忽略(new 优先级更高)
Function.prototype.myBind = function (thisArg, ...preset) {
  // TODO
};

参考答案(原理 + 代码)#

目标:实现简化版 bind,同时正确处理两类调用:

  • 直接调用:bound() → this 应该是 thisArg
  • 构造调用:new bound() → this 应该是新实例,忽略 thisArg
原理拆解(为什么要判断 instanceof)

new bound() 会先创建新对象,然后以新对象作为 this 调用 bound。因此在 bound 里可用 this instanceof bound 判断是否构造调用,从而选择正确的 this。

原型链处理(面试常追问)

为了让 new bound() 创建的实例能够访问 target.prototype 上的成员,需要让:

  • bound.prototype 的原型指向 target.prototype
Function.prototype.myBind = function (thisArg, ...preset) {
  const target = this;
  function bound(...args) {
    const isNew = this instanceof bound;
    const ctx = isNew ? this : thisArg;
    return target.apply(ctx, [...preset, ...args]);
  }
  // 让 new bound() 的实例能够沿原型链访问 target.prototype
  if (target.prototype) {
    bound.prototype = Object.create(target.prototype);
    Object.defineProperty(bound.prototype, 'constructor', {
      value: bound,
      writable: true,
      configurable: true,
    });
  }
  return bound;
};

题 5:实现 myInstanceof#

function myInstanceof(obj, Ctor) {
  // TODO
}

参考答案(原理 + 代码)#

原理:obj instanceof Ctor 等价于判断 Ctor.prototype 是否出现在 obj 的原型链上(沿 [[Prototype]] 向上查找)。

function myInstanceof(obj, Ctor) {
  if (obj == null) return false;
  if (typeof Ctor !== 'function') {
    throw new TypeError('Right-hand side of instanceof is not callable');
  }
  const proto = Ctor.prototype;
  let p = Object.getPrototypeOf(obj);
  while (p !== null) {
    if (p === proto) return true;
    p = Object.getPrototypeOf(p);
  }
  return false;
}
加分边界
  • 规范里还支持 Ctor[Symbol.hasInstance] 自定义 instanceof 行为;面试可补一句“我这里实现的是默认原型链语义”。

10 面试官常见追问(你要准备的边界)#

  • Promise 链式调用的错误如何传播?(throw/return Promise.reject)
  • 为什么 async 函数返回值会被包成 Promise?
  • Object.create(null) 用于什么场景?(字典/Map 替代,避免原型污染)

11 本篇小结(你应该掌握什么)#

  • 能画出调用栈解释闭包与 TDZ
  • 能用“调用点”解释 this
  • 能用“原型链包含关系”解释 instanceof
  • 能用“本轮宏任务结束清空微任务”解释事件循环

相关阅读

基于内容相似度 + 标签/系列加权

  • 2026年4月1日 01:00:005 分钟前端面试准备(8 年一线工程师知识体系与学习路线)
    01

    前端面试准备 00:知识体系地图(8 年工程师版)与复习节奏

    给你一张可执行的“面试知识体系地图”:从 JS/浏览器/网络/React/工程化/性能/安全/架构到项目与行为面。附 2/4/8 周复习节奏与每一块的验收标准。

    • 前端面试
    • 学习路线
    • 方法论
  • 2026年4月2日 04:00:003 分钟前端面试准备(8 年一线工程师知识体系与学习路线)
    02

    前端面试准备 02:TypeScript 工程能力——类型系统、泛型、条件类型与“类型体操”的边界

    TS 面试别只讲 keyof/extends:要能说清类型系统能力边界、如何在团队里落地、如何避免类型体操失控。附高频类型题与工程规范。

    • 前端面试
    • TypeScript
    • 工程化
  • 2026年4月3日 06:30:002 分钟前端面试准备(8 年一线工程师知识体系与学习路线)
    03

    前端面试准备 03:浏览器与网络——渲染流水线、缓存、CORS、Cookie、HTTP/2(高频追问一网打尽)

    把浏览器与网络题整理成“从输入到像素”的链路:渲染流水线、回流重绘、合成、缓存策略、CORS、Cookie/SameSite、HTTP/2/3。

    • 前端面试
    • 浏览器
    • 网络
  • 2026年4月4日 08:30:002 分钟前端面试准备(8 年一线工程师知识体系与学习路线)
    04

    前端面试准备 04:React 核心——渲染、更新、Hooks 心智模型、并发与 SSR(面试表达模板)

    用“渲染=计算 UI”与“更新=调度”两句话讲清 React:Hooks 规则、闭包陷阱、性能优化点、并发渲染与 SSR/Streaming 的取舍。

    • 前端面试
    • React
    • Hooks
返回文章列表

目录

27
  1. 01 执行上下文:你要能画出栈
  2. 高频追问
  3. 02 闭包:不是“函数套函数”,是“携带了外部环境”
  4. 实战落地
  5. 03 this:把绑定规则背成一张表
  6. 04 原型链:属性查找与继承的真实路径
  7. 05 事件循环:宏任务/微任务不是背分类,是理解“何时入队”
  8. 06 自测清单(60 秒版本)
  9. 07 深入:把 4 个核心模型讲清(面试可用表述)
  10. A. 调用栈(Call Stack)
  11. B. 作用域与词法环境(Lexical Environment)
  12. C. 原型链(Prototype Chain)
  13. D. 任务队列(Task Queues)
  14. 08 笔试题(选择 / 问答 / 输出)
  15. 题 1(输出题):变量提升与 TDZ
  16. 参考答案(原理)
  17. 题 2(输出题):this 绑定与引用丢失
  18. 参考答案(原理)
  19. 题 3(输出题):事件循环
  20. 参考答案(原理)
  21. 09 编码题(手写实现 + 推导)
  22. 题 4:实现 `myBind`
  23. 参考答案(原理 + 代码)
  24. 题 5:实现 `myInstanceof`
  25. 参考答案(原理 + 代码)
  26. 10 面试官常见追问(你要准备的边界)
  27. 11 本篇小结(你应该掌握什么)
进入沉浸式阅读