前端面试真题深度解析:从原型到安全,七道题看透核心能力

1. JS 的原型

问题: 能讲讲 JavaScript 的原型机制吗?__proto__prototype 有什么区别?

回答:

JavaScript 是基于原型的面向对象语言,没有传统类的概念(ES6 的 class 只是语法糖)。它的继承机制依赖于"原型链"。

每个函数都有一个 prototype 属性,它指向一个对象,这个对象就是该函数作为构造函数时,其实例的原型。而每个对象都有一个内部属性 [[Prototype]],在浏览器中通常暴露为 __proto__,它指向其构造函数的 prototype

当访问一个对象的属性时,如果该对象本身没有,就会沿着 __proto__ 向上查找,直到 null,这就是原型链。

js 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.name}`);
};

const p1 = new Person('Alice');
p1.sayHello(); // "Hello, I'm Alice"

原型链查找过程

%% 添加动画演示原型查找过程 sequenceDiagram participant JS as JavaScript引擎 participant I as 实例p1 participant P as Person.prototype JS->>I: p1.sayHello() alt 方法存在 I-->>JS: 直接执行 ✅ else 不存在 I->>P: 查找 __proto__ P->>P: 找到 sayHello() P-->>JS: 执行并返回结果 end

图解:

  • A → B :尝试调用 p1.sayHello(),JS 引擎首先检查 p1 自身是否有该属性。
  • B → C → D :没有,于是通过 __proto__ 指向其构造函数 Personprototype 对象。
  • D → E → G :在 Person.prototype 上找到了 sayHello 方法,绑定 thisp1 并执行。

关键点:

  • prototype 是函数才有的属性,用于实例继承。
  • __proto__ 是对象的内部原型引用(现代应使用 Object.getPrototypeOf())。
  • 所有对象最终原型链都会指向 Object.prototype,其 __proto__null

2. 变量作用域链

问题: 什么是作用域链?它在变量查找中起什么作用?

回答:

作用域链是 JavaScript 用来确定变量访问权限的机制。它是在函数定义时就确定的,基于词法作用域(静态作用域),而不是函数调用时。

当一个函数被定义,它会"记住"自己所在的作用域环境,形成一个作用域链。在查找变量时,从当前作用域开始,逐层向外查找,直到全局作用域。

js 复制代码
const x = 1;

function outer() {
  const y = 2;
  function inner() {
    const z = 3;
    console.log(x, y, z); // 1, 2, 3
  }
  inner();
}
outer();

作用域链变量查找

%% 动态演示作用域查找过程(可用于 Live Editor 播放) sequenceDiagram participant Engine as JS引擎 participant Inner as inner() participant Outer as outer() participant Global as 全局 Engine->>Inner: 调用 inner() Inner->>Inner: 创建执行上下文 Inner->>Inner: 查找 x Inner->>Outer: 沿作用域链查找 x Outer->>Global: 继续查找 x Global-->>Inner: 返回 x=1 Inner->>Outer: 查找 y Outer-->>Inner: 返回 y=2 Inner->>Inner: 找到 z=3 Inner-->>Engine: 输出 1,2,3

图解:

  • A → Binner 执行时,创建执行上下文,其作用域链包含:inner 自身 → outer → 全局。
  • C → D → E → F → G → Hx 不在 innerouter 中定义,最终在全局找到。
  • I → Jyouter 中定义,沿链找到。
  • K → Lzinner 中定义,直接使用。

关键点:

  • 作用域链在函数定义时确定,与调用位置无关。
  • 闭包的本质就是函数保留了对外部作用域的引用,即使外部函数已执行完毕。

3. call、apply、bind 的区别

问题: callapplybind 有什么区别?什么时候用哪个?

回答:

三者都用于改变函数执行时的 this 指向,但调用方式和返回值不同。

  • call(thisArg, arg1, arg2, ...): 立即执行函数,参数逐个传入。
  • apply(thisArg, [argsArray]): 立即执行函数,参数以数组形式传入。
  • bind(thisArg, arg1, arg2, ...): 返回一个新函数,不会立即执行,可后续调用。
js 复制代码
function greet(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: 'Bob' };

greet.call(person, 'Hi', '!');     // "Hi, I'm Bob!"
greet.apply(person, ['Hey', '?']); // "Hey, I'm Bob?"
const boundGreet = greet.bind(person, 'Hello');
boundGreet('.'); // "Hello, I'm Bob."

三者调用对比

sequenceDiagram participant Caller participant greet as greet函数 participant person as person对象 Caller->>greet: call(person, 'Hi', '!') greet-->>Caller: 立即执行,this=person Caller->>greet: apply(person, ['Hey', '?']) greet-->>Caller: 立即执行,this=person Caller->>greet: bind(person, 'Hello') greet-->>Caller: 返回新函数 boundGreet Caller->>boundGreet: boundGreet('.') boundGreet-->>Caller: 执行,this=person

图解:

  • callapply 都是立即调用,区别仅在参数形式。
  • bind延迟绑定 ,返回一个 this 已固定的新函数,适合事件回调、setTimeout 等场景。

实战建议:

  • 数组参数用 apply(如 Math.max.apply(null, arr))。
  • 需要预设部分参数时用 bind(柯里化)。
  • call 更通用,参数明确时优先。

4. 防抖和节流的区别

问题: 防抖和节流有什么区别?分别适用什么场景?

回答:

两者都是控制函数执行频率的手段,用于优化高频触发事件(如 resize、scroll、input)。

  • 防抖(Debounce):事件频繁触发时,只执行最后一次。如果持续触发,执行会被不断推迟。
  • 节流(Throttle):事件触发后,在一定时间窗口内只执行一次,之后可再次执行。
js 复制代码
// 防抖
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer); // 🔍 清除上一次未执行的定时器
    timer = setTimeout(() => {
      fn.apply(this, args); // 🔍 保证 this 和参数正确传递
    }, delay);
  };
}

// 节流(定时器版)
function throttle(fn, delay) {
  let timer = null;
  return function (...args) {
    if (timer) return; // 🔍 如果已有定时器,跳过本次
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null; // 🔍 执行后释放锁
    }, delay);
  };
}

Mermaid 状态图:防抖 vs 节流

stateDiagram-v2 [*] --> Idle state Debounce { Idle --> Pending: 事件触发 Pending --> Idle: delay 内无新事件 → 执行 Pending --> Pending: 新事件触发 → 重置定时器 } state Throttle { Idle --> Executing: 事件触发 Executing --> Cooldown: 执行函数 Cooldown --> Idle: delay 时间到 Executing --> Executing: 事件触发 → 忽略 } [*] --> Debounce [*] --> Throttle

图解:

  • 防抖:每次触发都重置计时,只有"静默期"结束后才执行。适合搜索框输入、窗口停止调整后执行。
  • 节流 :保证在 delay 时间内最多执行一次。适合滚动加载、按钮防重复点击。

踩坑提醒:

  • 防抖在持续触发时可能永远不执行,需结合业务判断。
  • 节流还有"时间戳版",性能更好但首次执行时机不同。

5. 介绍各种异步方案

问题: JS 异步发展经历了哪些阶段?Promise、async/await 解决了什么问题?

回答:

JS 异步经历了回调 → Promise → async/await 的演进,核心是解决"回调地狱"和错误处理问题。

js 复制代码
// 1. 回调(Callback Hell)
getData(function (a) {
  getMoreData(a, function (b) {
    getEvenMoreData(b, function (c) {
      console.log(c);
    });
  });
});

// 2. Promise(链式调用)
getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => console.log(c))
  .catch(err => console.error(err)); // 🔍 统一错误处理

// 3. async/await(同步写法)
async function fetchData() {
  try {
    const a = await getData();
    const b = await getMoreData(a);
    const c = await getEvenMoreData(b);
    console.log(c);
  } catch (err) {
    console.error(err); // 🔍 错误冒泡,像同步代码一样处理
  }
}

异步演进对比

%% ========================================================= %% 专业级 JavaScript 事件循环时序图 %% 主题:宏任务 / 微任务 / async-await 全流程 %% 适配:深色 / 浅色模式自适应 %% 图标:FontAwesome 6.5 %% ========================================================= %% 1. 全局样式 %% 2. 参与者定义 %% 3. 消息流 %% 4. 高亮与分组 %% 5. 动效提示(hover 说明) %% ========== 1. 全局样式 ========== %% 主色:#0ea5e9(sky-500) %% 辅色:#10b981(emerald-500) %% 警告:#f59e0b(amber-500) %% 背景:#ffffff / #111827(自适应) %% 字体:Inter, system-ui, sans-serif %% 圆角:8px %% 阴影:0 4px 6px -1px rgba(0,0,0,0.1) %% ========== 2. 参与者定义 ========== sequenceDiagram autonumber participant Main as 主线程 participant API as Web API participant MacroQ as 宏任务队列 participant MicroQ as 微任务队列 participant AsyncF as async 函数 %% ========== 3. 消息流 ========== rect rgb(14,165,233,0.1) note left of Main: ① 经典回调 Main ->> API : setTimeout / XHR(回调) API -->> MacroQ: 完成后入队 MacroQ->>Main : 事件循环取出 Main ->>Main : 执行回调 end rect rgb(16,185,129,0.1) note left of Main: ② Promise.then Main ->> API : Promise.then() API -->> MicroQ: 微任务入队 MicroQ->>Main : 立即优先执行 Main ->>Main : then 回调 end rect rgb(245,158,11,0.1) note left of Main: ③ async/await Main ->> AsyncF: await promise Main ->> Main : 暂停函数,不阻塞线程 API -->> MicroQ: promise 完成后 MicroQ->>AsyncF : 恢复执行 end %% ========== 4. 高亮与分组 ========== %% 关键消息高亮 activate Main note over Main: 事件循环持续检查队列 deactivate Main %% ========== 5. 动效提示(hover 说明) ========== %% 部署时可通过 CSS 或 JS 实现: %% .participantBox[id*="Main"]:hover::after { content: "主线程单线程执行 JS"; } %% .participantBox[id*="MicroQ"]:hover::after { content: "微任务队列优先级高于宏任务"; }

图解:

  • 回调:嵌套深,错误处理分散。
  • Promise :扁平化,支持链式调用和 .catch()
  • async/await :用同步语法写异步,可配合 try/catch,可读性最强。

关键点:

  • Promise 是微任务,比 setTimeout(宏任务)优先执行。
  • await 只能在 async 函数内使用。
  • 错误必须用 try/catch 捕获,否则会变成未处理的 Promise rejection

6. XSS 与 CSRF

问题: 说说 XSS 和 CSRF 的区别?如何防范?

回答:

XSS(跨站脚本攻击):攻击者注入恶意脚本,当其他用户浏览页面时执行,窃取 cookie、session 等。

CSRF(跨站请求伪造):攻击者诱导用户在已登录状态下发起非本意的请求,如转账、发帖。

html 复制代码
<!-- XSS 示例:注入脚本 -->
<script>
  fetch('/api/steal-cookie', {
    method: 'POST',
    body: document.cookie // 🔍 窃取用户 cookie
  });
</script>

<!-- CSRF 示例:诱导提交表单 -->
<img src="http://bank.com/transfer?to=attacker&amount=1000" width="0">

XSS 与 CSRF 攻击路径

%% ========================================================= %% 专业级 Web 安全威胁对比图 %% 主题:XSS vs CSRF 攻击链可视化 %% 适配:深色 / 浅色模式自适应 %% 图标:FontAwesome 6.5 %% ========================================================= %% 1. 全局样式 %% 2. 节点定义 %% 3. 连线定义 %% 4. 子图分组 %% 5. 动效提示(hover 说明) %% ========== 1. 全局样式 ========== %% 主色:#ef4444(red-500)- 攻击 %% 辅色:#f97316(orange-500)- 中间步骤 %% 成功:#10b981(emerald-500)- 攻击达成 %% 背景:#ffffff / #111827(自适应) %% 字体:Inter, system-ui, sans-serif %% 圆角:8px %% 阴影:0 4px 6px -1px rgba(0,0,0,0.1) %% ========== 2. 节点定义 ========== graph LR classDef attack fill:#ef4444,stroke:#dc2626,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef step fill:#f97316,stroke:#ea580c,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef success fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef user fill:#3b82f6,stroke:#2563eb,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef server fill:#6366f1,stroke:#4f46e5,color:#fff,stroke-width:2px,rx:8px,ry:8px %% XSS 攻击链 A[" 攻击者提交含 script 的内容"]:::attack B[" 服务器存储或返回"]:::server C[" 用户访问页面"]:::user D[" 浏览器执行恶意脚本"]:::step E[" 窃取数据或冒充用户"]:::success %% CSRF 攻击链 F[" 用户登录 bank.com"]:::user G[" 用户访问 attacker.com"]:::attack H[" 页面自动发起 bank.com 请求"]:::step I[" 浏览器携带 cookie"]:::step J[" bank.com 认为是合法请求"]:::server K[" 执行转账等操作"]:::success %% ========== 3. 连线定义 ========== A --> B B --> C C --> D D --> E F -.-> G G --> H H --> I I --> J J --> K %% ========== 4. 子图分组 ========== subgraph XSS 攻击链 direction LR A B C D E end subgraph CSRF 攻击链 direction LR F G H I J K end %% ========== 5. 动效提示(hover 说明) ========== %% 注:Mermaid 暂不支持原生 hover,以下为建议实现方式 %% 实际部署时可通过 CSS 或 JS 实现: %% .node[id^="A"]:hover::after { content: "攻击者通过输入框、评论等提交恶意脚本"; } %% .node[id^="E"]:hover::after { content: "可窃取 Cookie、Session、DOM 数据或执行任意操作"; } %% .node[id^="K"]:hover::after { content: "利用用户已登录状态,执行非预期操作"; } %% 关键路径高亮 linkStyle 0 stroke:#ef4444,stroke-width:3px linkStyle 1 stroke:#f97316,stroke-width:3px linkStyle 2 stroke:#3b82f6,stroke-width:3px linkStyle 3 stroke:#10b981,stroke-width:3px linkStyle 4 stroke:#3b82f6,stroke-width:3px,stroke-dasharray: 5 5 linkStyle 5 stroke:#ef4444,stroke-width:3px linkStyle 6 stroke:#f97316,stroke-width:3px linkStyle 7 stroke:#6366f1,stroke-width:3px linkStyle 8 stroke:#10b981,stroke-width:3px

图解:

  • XSS :攻击目标是用户浏览器,利用信任执行脚本。
  • CSRF :攻击目标是服务器接口,利用用户身份伪造请求。

防范措施:

  • XSS
    • 输入转义(HTML 实体编码)
    • 使用 CSP(Content Security Policy)
    • 设置 HttpOnly cookie(防止 JS 读取)
  • CSRF
    • 使用 SameSite cookie 属性(推荐 StrictLax
    • 验证 Referer / Origin
    • 关键操作使用 Token(如 CSRF Token)

7. HTTP 缓存控制

问题: HTTP 缓存有哪些机制?强缓存和协商缓存有什么区别?

回答:

HTTP 缓存分为强缓存协商缓存,浏览器优先使用强缓存,失效后再走协商缓存。

  • 强缓存 :不发请求,直接使用本地缓存。由 Cache-ControlExpires 控制。
  • 协商缓存 :发请求,由服务器判断是否更新。由 ETag/If-None-MatchLast-Modified/If-Modified-Since 实现。
http 复制代码
# 响应头(服务器设置)
Cache-Control: max-age=3600
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
%% 专业级 HTTP 缓存流程图 %% 作者:Mermaid 可视化架构师 %% 主题:HTTP 缓存机制(强缓存 + 协商缓存) %% 适配:深色 / 浅色模式自适应 %% 图标:FontAwesome 6.5 %% 1. 全局样式 %% 2. 节点定义 %% 3. 连线定义 %% 4. 子图分组 %% ========== 1. 全局样式 ========== %% 使用 HSL 色轮生成协调色系 %% 主色:#0ea5e9(sky-500) %% 辅色:#10b981(emerald-500) %% 警告:#f59e0b(amber-500) %% 异常:#ef4444(red-500) %% 背景:#ffffff / #111827(自适应) %% 字体:Inter, system-ui, sans-serif %% 圆角:8px %% 阴影:0 4px 6px -1px rgba(0,0,0,0.1) %% ========== 2. 节点定义 ========== graph TD classDef default fill:#0ea5e9,stroke:#0284c7,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef decision fill:#f59e0b,stroke:#d97706,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef success fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef error fill:#ef4444,stroke:#dc2626,color:#fff,stroke-width:2px,rx:8px,ry:8px classDef cache fill:#8b5cf6,stroke:#7c3aed,color:#fff,stroke-width:2px,rx:8px,ry:8px %% 节点定义(带图标) A[" 发起请求"]:::default B{" 强缓存有效?"}:::decision C[" 直接使用本地缓存"]:::success D[" 发送请求,带协商头"]:::default E[" 服务器比对 ETag / Last-Modified"]:::default F[" 返回 304 Not Modified"]:::success G[" 返回 200 + 新资源"]:::cache H[" 使用本地缓存"]:::success I[" 更新缓存"]:::cache %% ========== 3. 连线定义 ========== A --> B B -- 是 --> C B -- 否 --> D D --> E E -- 未改变 --> F E -- 已改变 --> G F --> H G --> I %% ========== 4. 子图分组 ========== subgraph 强缓存阶段 A B C end subgraph 协商缓存阶段 D E F G H I end %% 关键路径高亮 linkStyle 0 stroke:#0ea5e9,stroke-width:3px linkStyle 1 stroke:#10b981,stroke-width:3px linkStyle 2 stroke:#f59e0b,stroke-width:3px linkStyle 3 stroke:#0ea5e9,stroke-width:3px linkStyle 4 stroke:#10b981,stroke-width:3px linkStyle 5 stroke:#8b5cf6,stroke-width:3px linkStyle 6 stroke:#10b981,stroke-width:3px linkStyle 7 stroke:#8b5cf6,stroke-width:3px

图解:

  • B → Cmax-age 未过期,直接读本地缓存,不发请求。
  • D → E → F → H:强缓存过期,发请求,服务器发现资源未变,返回 304,浏览器继续用旧资源。
  • D → E → G → I:资源已更新,返回新内容并更新缓存。

关键点:

  • Cache-Control 优先级高于 Expires
  • ETag 精度更高(内容哈希),Last-Modified 可能因秒级精度误判。
  • 静态资源建议设置长期强缓存 + 文件名哈希(如 app.a1b2c3.js),避免更新问题。

相关推荐
丘山子12 分钟前
Python 布尔运算的优雅实践
后端·python·面试
前端小咸鱼一条24 分钟前
React组件化的封装
前端·javascript·react.js
随便起的名字也被占用32 分钟前
leaflet中绘制轨迹线的大量轨迹点,解决大量 marker 绑定 tooltip 同时显示导致的性能问题
前端·javascript·vue.js·leaflet
南方kenny38 分钟前
TypeScript + React:让前端开发更可靠的黄金组合
前端·react.js·typescript
Cache技术分享41 分钟前
149. Java Lambda 表达式 - Lambda 表达式的序列化
前端·后端
LaoZhangAI1 小时前
GPT-5推理能力全解析:o3架构、链式思考与2025年8月发布
前端·后端
海风极客1 小时前
Go内存逃逸分析,真的很神奇吗?
后端·面试
JuneXcy1 小时前
11.Layout-Pinia优化重复请求
前端·javascript·css
子洋1 小时前
快速目录跳转工具 zoxide 使用指南
前端·后端·shell