前端面试八股文:JS 原理、框架与安全
前端面试八股文:JS 原理、框架与安全
从记忆到理解,本教程将带你深入前端面试中的“经典八股题”,覆盖 JavaScript 运行机制、现代框架核心思想以及必备的 Web 安全知识。结构清晰、案例丰富,助你从根源上搞懂每一个考点,实现举一反三。
JavaScript 核心原理
这部分将剖析面试中高频出现的 JS 底层机制,告别死记硬背,建立稳固的执行上下文与内存模型。
执行上下文与调用栈
理解代码运行时的环境创建与销毁过程,是解读作用域、闭包和异步行为的钥匙。
-
执行上下文(Execution Context) 任何 JavaScript 代码都是在执行上下文中运行的。主要有三种类型:
- 全局执行上下文:代码首次运行时创建,一个程序中只有一个。
- 函数执行上下文:每当一个函数被调用时,都会创建一个新的上下文。
- Eval 执行上下文:
eval()函数内部的代码,极少使用。
-
执行上下文的生命周期
- 创建阶段:
- 生成变量对象(Variable Object,VO),包含 arguments、函数声明(整体提升)、变量声明(
var提升但未赋值,值为undefined)。 - 建立作用域链(Scope Chain)。
- 确定
this的指向。
- 生成变量对象(Variable Object,VO),包含 arguments、函数声明(整体提升)、变量声明(
- 执行阶段:
- 变量赋值、函数引用、执行代码。
- 销毁阶段:
- 执行完毕,上下文出栈,变量对象被回收。
- 创建阶段:
-
调用栈(Call Stack) 一种 LIFO(后进先出)的数据结构,用于管理函数的调用关系。每当函数被调用,其上下文被推入栈顶;当函数执行结束,栈顶上下文弹出。栈溢出(Stack Overflow)通常由递归过深引起。
常见面试题:解释一下变量提升和函数提升的区别。
- 函数声明会被整体提升(函数名和函数体),可以在声明前调用。
var声明变量提升但值为undefined,let/const存在暂时性死区,不会提前初始化。
作用域与闭包
闭包是 JavaScript 中的难点与高频考点,其根本在于词法作用域和垃圾回收。
-
词法作用域(静态作用域) 函数的作用域在定义时就已经决定,与调用位置无关。寻找变量时,从自身作用域开始,逐级向上层作用域查找,直到全局。
-
闭包(Closure) 一个函数和对其周围状态(词法环境)的引用捆绑在一起,这样的组合就是闭包。简单说,一个内部函数能够访问其外部函数的作用域,即使外部函数已经执行完毕。
function outer() { let count = 0; return function inner() { count++; console.log(count); }; } const counter = outer(); counter(); // 1 counter(); // 2闭包的本质:内部函数被返回并在外部持有引用,导致外部函数的词法环境无法被垃圾回收。 应用场景:模块化、封装私有变量、函数柯里化、防抖节流。 内存泄漏风险:不合理地维持闭包会导致变量常驻内存,应在不再需要时手动解除引用(如
counter = null)。
原型与原型链
JavaScript 依靠原型实现继承,这是面试中的基础考查点。
-
构造函数与原型对象
- 每个函数都有一个
prototype属性,指向一个对象(原型对象)。 - 原型对象上有一个
constructor属性,指回构造函数本身。 - 构造函数创建的实例,通过
__proto__(或Object.getPrototypeOf)指向构造函数的prototype。
- 每个函数都有一个
-
原型链 访问对象的属性时,先在自身查找;若没有,则沿
__proto__向上查找,直到Object.prototype,最后为null。这条查找链路就是原型链。 -
继承方式
- 原型链继承:子类原型指向父类实例,缺点:引用值共享、无法传参。
- 借用构造函数:用
call调用父类构造,缺点:方法无法复用。 - 组合继承:原型链继承方法,借用构造函数继承属性,但会调用两次父类构造。
- 寄生组合继承(最优):通过创建父类原型的副本并修正
constructor来实现,ES6class extends的本质。
面试高频:手写 instanceof 的实现。
思路:沿着左边对象的原型链 __proto__ 查找,判断是否等于右边构造函数的 prototype。
事件循环与异步编程
前端必须掌握 JavaScript 的单线程模型与异步调度策略。
-
宏任务与微任务
- 宏任务(Macro Task):
script(整体代码)、setTimeout、setInterval、I/O、UI 渲染等。 - 微任务(Micro Task):
Promise.then/catch/finally、MutationObserver、queueMicrotask。 - 执行顺序:每次宏任务执行完毕后,立即清空当前微任务队列中的所有微任务,然后再从宏任务队列中取下一个任务。
- 宏任务(Macro Task):
-
事件循环流程
- 执行同步代码,这本身是一个宏任务。
- 当宏任务执行完毕,检查微任务队列并依次执行。
- 有需要则进行窗口渲染。
- 取下一个宏任务,重复上述循环。
-
Async/Await 本质是 Generator 和 Promise 的语法糖。
async函数返回一个 Promise;await后面的表达式会等待 Promise 状态变化后的值,下面的代码会被放入微任务队列(相当于Promise.then)。
常见考题:请写出 setTimeout、Promise、async/await 混用时的输出顺序。这需要彻底理解微任务在 await 处的拆分。
现代框架核心概念
以 React 和 Vue 为例,深入它们的渲染机制、响应式原理和设计模式。面试官考核的不是 API 记忆,而是“为什么这么设计”。
虚拟 DOM 与 Diff 算法
操作真实 DOM 成本高昂,虚拟 DOM 提供了高效的更新策略。
- 虚拟 DOM 是什么
用 JavaScript 对象来描述真实 DOM 节点的层级与属性。如
{ tag: 'div', props: { id: 'app' }, children: [...] }。 - 渲染流程
- 模板/JSX 编译为渲染函数,执行后生成虚拟 DOM 树。
- 状态变化时,生成新的虚拟 DOM 树。
- 通过 Diff 算法比较新旧两棵树,找出需更新的最小差异(Patch)。
- 将 Patch 应用到真实 DOM。
- 核心 Diff 策略
- 同层比较:只比较同一层级节点,跨层级移动视为删除和新增。
- 类型判断:节点类型不同则直接替换整棵子树。
- Key 的重要性:在同层级子节点列表中,通过 key 来标识节点,实现最小移动和复用。Key 应稳定、唯一,避免用索引。
- Vue 与 React 的差异
- React:递归比对,强调纯函数,更新粒度在组件树。
- Vue:编译阶段标记静态节点和动态绑定,比对时跳过静态内容;更新粒度在组件级别,并采用“靶向更新”。
响应式原理
数据驱动视图,必须理解数据变化如何自动触发视图更新。
- Vue 2.x Object.defineProperty
- 遍历每个属性,通过
getter进行依赖收集,setter触发更新。 - 局限性:无法侦测对象属性的添加/删除、数组索引赋值和长度变化(因此需要
Vue.set/delete)。
- 遍历每个属性,通过
- Vue 3.x Proxy
- 直接代理整个对象,可以拦截包括属性读取、新增、删除在内的 13 种操作。
- 天然支持数组索引和
length变化,也无需递归到对象的每一个属性。 Reflect配合使用保证this指向正确。
- React 的不可变数据
- React 通过状态引用变化来触发更新(如
setState传入新对象)。 - 数据是不可变的,内部使用
Object.is做浅比较判断是否需要重新计算子树。 - 核心思想:让数据可变(Vue)带来便利,但也可能带来追踪负担;数据不可变(React)让变化可预测,但需要编写更多更新逻辑。
- React 通过状态引用变化来触发更新(如
组件化与设计模式
现代框架的组件设计思想贯穿于整个面试。
- 单向数据流 父组件通过 props 传递数据给子组件,子组件不能直接修改 props。要改变数据,必须通过回调函数向父组件传递事件,由父组件更新状态。这保证了数据变化可追踪。
- 状态提升 当多个组件需要共享同一份状态时,将该状态移动到它们最近的共同父组件中管理。
- 组合优于继承
React 强烈的哲学:通过
children、Render Props、HOC 等组合方式复用逻辑,而不是创建深层组件继承层级。Vue 也推崇插槽(slot)和混入/组合式函数。 - 高阶组件(HOC)与 Hooks
- HOC:一个函数,接收组件并返回新组件,用于逻辑复用。可能导致“包装地狱”。
- React Hooks:在函数组件里“钩入”状态和生命周期,使逻辑复用更轻量(自定义 Hook)。
- Vue 3 Composition API:与 Hooks 理念类似,通过
setup函数聚合相关逻辑,取代 Options API 的碎片化倾向。
前端安全实践
安全不仅是常识,更是区分高级工程师的关键模块。面试中常考的几种攻击与防御必须滚瓜烂熟。
XSS 跨站脚本攻击
攻击者在目标网站注入恶意脚本,当用户浏览时脚本执行。
- 攻击类型
- 存储型 XSS:恶意脚本永久存储在服务器(数据库、留言板等),用户访问页面时执行。危害最大。
- 反射型 XSS:脚本包含在 URL 参数中,服务器将其反射回响应。需要诱导用户点击链接。
- DOM 型 XSS:纯客户端漏洞,通过修改页面 DOM 节点注入脚本(如
innerHTML、document.write接收不干净数据)。
- 防御手段
- 输出编码:根据输出位置(HTML 标签、属性、JavaScript 变量、CSS、URL),对数据进行相应转义处理。
- 内容安全策略(CSP):通过 HTTP 头部或
meta标签,定义允许加载资源的域,禁止内联脚本的执行。 - HttpOnly Cookie:设置 Cookie 为 HttpOnly,使得 JavaScript 无法通过
document.cookie读取,减少 cookie 泄露。 - 输入验证:前后端都应对用户输入做长度、格式限制,拒绝可疑内容。
- React/Vue 自动转义:框架在 JSX/模板中输出变量时默认进行 HTML 转义。但需注意
dangerouslySetInnerHTML(React)和v-html(Vue)的使用,避免传入用户可控数据。
CSRF 跨站请求伪造
劫持已认证用户的浏览器发送恶意请求给受信任的站点。
- 攻击原理
用户登录了银行网站 A,Cookie 保存在浏览器中。在未退出 A 的情况下访问恶意网站 B。B 站包含一个请求,比如
<img src="http://bank.com/transfer?amount=1000&to=hacker">。浏览器带着 A 的 Cookie 发出请求,银行误以为用户授权操作。 - 防御策略
- Referer / Origin 校验:检测请求头中的来源是否为可信域名,但可能被篡改或缺失。
- Token 校验:服务端生成一个随机 CSRF Token,嵌入在页面的表单或请求头中,每次请求必须携带 Token,并验证。Token 不与 Cookie 自动发送。
- SameSite Cookie:设置 Cookie 属性
SameSite=Strict或Lax,限制跨站请求携带 Cookie。Strict完全禁止,Lax允许部分(如链接跳转)携带。 - 验证码/二次验证:对敏感操作强制要求用户交互。
其他常见安全问题
- 点击劫持(Clickjacking)
攻击者使用透明的 iframe 覆盖在诱人点击的按钮上,用户实际点击了隐藏页面中的按钮。
- 防御:通过
X-Frame-Options头部(DENY/SAMEORIGIN)或 CSP 的frame-ancestors指令,禁止页面被放入 iframe。
- 防御:通过
- 中间人攻击与 HTTPS
HTTP 明文传输,攻击者可窃听、篡改通信内容。
- 升级 HTTPS:使用 TLS 加密,验证服务器身份,保证数据机密性与完整性。
- HSTS:强制客户端使用 HTTPS 连接,避免 SSLstrip 降级攻击。
- 依赖安全
通过
npm audit等工具扫描项目依赖的已知漏洞,及时升级和修复。
以上涵盖了前端面试中 JS 原理、框架核心与安全三大支柱的“八股文”内容。真正的掌握不是背诵,而是结合自身项目经验,将这些知识点串联成体系。建议结合手写代码、模拟场景深入理解每个原理背后的设计逻辑,让面试官看到解决问题时的扎实功底。