1. JS 的原型
问题: 能讲讲 JavaScript 的原型机制吗?__proto__ 和 prototype 有什么区别?
回答:
JavaScript 是基于原型的面向对象语言,没有传统类的概念(ES6 的 class 只是语法糖)。它的继承机制依赖于“原型链”。
每个函数都有一个 prototype 属性,它指向一个对象,这个对象就是该函数作为构造函数时,其实例的原型。而每个对象都有一个内部属性 [[Prototype]],在浏览器中通常暴露为 __proto__,它指向其构造函数的 prototype。
当访问一个对象的属性时,如果该对象本身没有,就会沿着 __proto__ 向上查找,直到 null,这就是原型链。
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__指向其构造函数Person的prototype对象。 - D → E → G:在
Person.prototype上找到了sayHello方法,绑定this为p1并执行。
关键点:
prototype是函数才有的属性,用于实例继承。__proto__是对象的内部原型引用(现代应使用Object.getPrototypeOf())。- 所有对象最终原型链都会指向
Object.prototype,其__proto__为null。
2. 变量作用域链
问题: 什么是作用域链?它在变量查找中起什么作用?
回答:
作用域链是 JavaScript 用来确定变量访问权限的机制。它是在函数定义时就确定的,基于词法作用域(静态作用域),而不是函数调用时。
当一个函数被定义,它会“记住”自己所在的作用域环境,形成一个作用域链。在查找变量时,从当前作用域开始,逐层向外查找,直到全局作用域。
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 → B:
inner执行时,创建执行上下文,其作用域链包含:inner自身 →outer→ 全局。 - C → D → E → F → G → H:
x不在inner或outer中定义,最终在全局找到。 - I → J:
y在outer中定义,沿链找到。 - K → L:
z在inner中定义,直接使用。
关键点:
- 作用域链在函数定义时确定,与调用位置无关。
- 闭包的本质就是函数保留了对外部作用域的引用,即使外部函数已执行完毕。
3. call、apply、bind 的区别
问题: call、apply、bind 有什么区别?什么时候用哪个?
回答:
三者都用于改变函数执行时的 this 指向,但调用方式和返回值不同。
call(thisArg, arg1, arg2, ...): 立即执行函数,参数逐个传入。apply(thisArg, [argsArray]): 立即执行函数,参数以数组形式传入。bind(thisArg, arg1, arg2, ...): 返回一个新函数,不会立即执行,可后续调用。
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
图解:
call和apply都是立即调用,区别仅在参数形式。bind是延迟绑定,返回一个this已固定的新函数,适合事件回调、setTimeout 等场景。
实战建议:
- 数组参数用
apply(如Math.max.apply(null, arr))。 - 需要预设部分参数时用
bind(柯里化)。 call更通用,参数明确时优先。
4. 防抖和节流的区别
问题: 防抖和节流有什么区别?分别适用什么场景?
回答:
两者都是控制函数执行频率的手段,用于优化高频触发事件(如 resize、scroll、input)。
- 防抖(Debounce):事件频繁触发时,只执行最后一次。如果持续触发,执行会被不断推迟。
- 节流(Throttle):事件触发后,在一定时间窗口内只执行一次,之后可再次执行。
// 防抖
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 的演进,核心是解决“回调地狱”和错误处理问题。
// 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 <i class="fa fa-microchip"></i> 主线程
participant API as <i class="fa fa-cogs"></i> Web API
participant MacroQ as <i class="fa fa-list-ol"></i> 宏任务队列
participant MicroQ as <i class="fa fa-list-ul"></i> 微任务队列
participant AsyncF as <i class="fa fa-magic"></i> 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(跨站请求伪造):攻击者诱导用户在已登录状态下发起非本意的请求,如转账、发帖。
<!-- 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["<i class='fa fa-user-ninja'></i> 攻击者提交含 script 的内容"]:::attack
B["<i class='fa fa-database'></i> 服务器存储或返回"]:::server
C["<i class='fa fa-user'></i> 用户访问页面"]:::user
D["<i class='fa fa-bolt'></i> 浏览器执行恶意脚本"]:::step
E["<i class='fa fa-mask'></i> 窃取数据或冒充用户"]:::success
%% CSRF 攻击链
F["<i class='fa fa-user-check'></i> 用户登录 bank.com"]:::user
G["<i class='fa fa-user-ninja'></i> 用户访问 attacker.com"]:::attack
H["<i class='fa fa-paper-plane'></i> 页面自动发起 bank.com 请求"]:::step
I["<i class='fa fa-cookie-bite'></i> 浏览器携带 cookie"]:::step
J["<i class='fa fa-shield-alt'></i> bank.com 认为是合法请求"]:::server
K["<i class='fa fa-money-bill-transfer'></i> 执行转账等操作"]:::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)
- 设置
HttpOnlycookie(防止 JS 读取)
- CSRF:
- 使用
SameSitecookie 属性(推荐Strict或Lax) - 验证
Referer/Origin头 - 关键操作使用 Token(如 CSRF Token)
- 使用
7. HTTP 缓存控制
问题: HTTP 缓存有哪些机制?强缓存和协商缓存有什么区别?
回答:
HTTP 缓存分为强缓存和协商缓存,浏览器优先使用强缓存,失效后再走协商缓存。
- 强缓存:不发请求,直接使用本地缓存。由
Cache-Control和Expires控制。 - 协商缓存:发请求,由服务器判断是否更新。由
ETag/If-None-Match或Last-Modified/If-Modified-Since实现。
# 响应头(服务器设置)
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["<i class='fa fa-paper-plane'></i> 发起请求"]:::default
B{"<i class='fa fa-question-circle'></i> 强缓存有效?"}:::decision
C["<i class='fa fa-check-circle'></i> 直接使用本地缓存"]:::success
D["<i class='fa fa-arrow-right'></i> 发送请求,带协商头"]:::default
E["<i class='fa fa-server'></i> 服务器比对 ETag / Last-Modified"]:::default
F["<i class='fa fa-info-circle'></i> 返回 304 Not Modified"]:::success
G["<i class='fa fa-download'></i> 返回 200 + 新资源"]:::cache
H["<i class='fa fa-save'></i> 使用本地缓存"]:::success
I["<i class='fa fa-sync-alt'></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 → C:
max-age未过期,直接读本地缓存,不发请求。 - D → E → F → H:强缓存过期,发请求,服务器发现资源未变,返回 304,浏览器继续用旧资源。
- D → E → G → I:资源已更新,返回新内容并更新缓存。
关键点:
Cache-Control优先级高于Expires。ETag精度更高(内容哈希),Last-Modified可能因秒级精度误判。- 静态资源建议设置长期强缓存 + 文件名哈希(如
app.a1b2c3.js),避免更新问题。