1. JavaScript 中的 9 种继承方式
✅ 方式一:原型链继承
js
function Parent() { this.colors = ['red']; }
function Child() {}
Child.prototype = new Parent(); // 缺点:引用共享
Q1: 多个 Child 实例修改 colors 会互相影响吗?
A1: 会!因为所有实例共享同一个原型上的引用属性 ❌
✅ 方式二:构造函数继承(经典借用)
js
function Child() {
Parent.call(this); // 每次都复制一份
}
Q2: 能继承 Parent.prototype 上的方法吗?
A2: 不能!方法都在实例上,浪费内存 💥
✅ 方式三:组合继承(最常用)
js
function Child() { Parent.call(this); }
Child.prototype = new Parent();
Child.prototype.constructor = Child;
👉继续看?Q3: 为什么 Parent 构造函数执行了两次?
A3: 一次在 Child 内部 call,一次在 prototype 初始化时 ------ 浪费性能🤯
✅ 方式四:寄生组合式继承(React/Vue 源码级写法)
js
function inherit(Child, Parent) {
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
}
// 使用
inherit(Child, Parent);
Object.create()
创建空对象并设置[[Prototype]]
,避免调用 Parent 构造函数
Q4: 为什么 Vue 3 之前用这种方式?
A4: 兼容 IE9+,性能最优,ES6 class 出现前的"黄金标准"💡
✅ 方式五:ES6 Class 继承
js
class Child extends Parent {
constructor() { super(); }
}
底层仍是寄生组合式 + [[HomeObject]]
机制
Q5: super() 和 Parent.call(this) 一样吗?
A5: 不一样!super() 会绑定 this 并触发父类构造,还支持静态继承 🤯
✅ 方式六:Object.setPrototypeOf()
js
const child = { __proto__: parent }; // 或 setPrototypeOf
性能差,不推荐生产使用
✅ 方式七:混入(Mixin)模式
js
function mixin(target, ...sources) {
sources.forEach(src => Object.assign(target, src));
}
Vue 2 的 mixins: []
就是这种,但已被 Composition API 取代
✅ 方式八:原型式继承(Object.create 的封装)
js
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
适合临时对象继承,如 Object.create(proto)
✅ 方式九:寄生式继承
js
function createAnother(original) {
const clone = Object.create(original);
clone.sayHi = function() { console.log('hi'); };
return clone;
}
增强对象,但函数无法复用
2. 深拷贝 vs 浅拷贝,Lodash 是怎么做到的?
js
// 浅拷贝:只复制第一层
const shallow = { ...obj };
// 深拷贝:递归复制所有层级
Lodash _.cloneDeep
原理(简化版):
js
function cloneDeep(value, hash = new WeakMap()) {
if (value == null || typeof value !== 'object') return value;
if (hash.has(value)) return hash.get(value); // 防环引用
let result;
if (Array.isArray(value)) {
result = [];
hash.set(value, result);
for (let i = 0; i < value.length; i++) {
result[i] = cloneDeep(value[i], hash); // 递归
}
} else {
result = {};
hash.set(value, result);
for (let key in value) {
if (value.hasOwnProperty(key)) {
result[key] = cloneDeep(value[key], hash);
}
}
}
return result;
}
⚠️这代码能处理 Date、RegExp 吗?
Q6: 如何处理循环引用?
A6: 用 WeakMap
缓存已拷贝对象,遇到重复引用直接返回缓存 💡
Q7: 为什么不用 JSON.parse(JSON.stringify())?
A7: 丢失函数、undefined、Symbol、循环引用报错、Date 变字符串 ❌
Q8: 性能瓶颈在哪?
A8: 递归 + WeakMap 查找,大数据结构会卡顿 👉继续看?
3. ES6 let 块作用域是怎么实现的?
js
{
let a = 1;
var b = 2;
}
console.log(b); // 2
console.log(a); // ReferenceError
V8 引擎内部:词法环境(Lexical Environment)栈
⚠️let 不会变量提升?其实是"暂时性死区"!
Q9: 为什么 let 在声明前访问会报错?
A9: 变量已绑定到环境记录,但未初始化,处于 TDZ(暂时性死区)💥
Q10: V8 怎么标记块级作用域?
A10: 编译阶段生成 Scope Info,运行时通过上下文栈管理嵌套作用域 🤯
4. 虚拟 DOM 到底是什么?做了什么?
虚拟 DOM = 用 JS 对象描述真实 DOM 结构
js
const vnode = {
tag: 'div',
props: { id: 'app' },
children: [
{ tag: 'span', children: 'Hello' }
]
};
⚠️vdom 真的比直接操作 dom 快吗?
Q11: 什么时候 vdom 性能更差?
A11: 单次简单操作(如改一个文本),直接 dom 更快 ❌
Q12: React 和 Vue 的 diff 策略有何不同?
A12: React 单节点比对 + key 优化;Vue 双端比对 + 预判优化 💡
5. 304 是什么?
HTTP 状态码
304 Not Modified
,协商缓存生效标志
流程:
⚠️ETag 和 Last-Modified 区别?
Q13: ETag 是怎么生成的?
A13: 文件内容哈希 or 时间戳+大小组合,精确控制变更检测 ✅
6. 打包时 Hash 码是怎么生成的?
Webpack 三种 hash:
hash
: 整个编译唯一chunkhash
: 按 chunk 分离contenthash
: 按文件内容(CSS 提取必备)
原理伪码:
js
const crypto = require('crypto');
function generateHash(content) {
return crypto.createHash('md5').update(content).digest('hex').slice(0,8);
}
⚠️为什么不用 Math.random()?
Q14: contenthash 如何保证内容不变 hash 就不变?
A14: 基于最终输出资源内容计算,Webpack 在 emit 阶段完成 ✅
7. 随机值重复怎么办?如何避免?
js
Math.random().toString(36).slice(2); // 可能重复!
✅ 解决方案:
Date.now() + Math.random()
→ 概率降低UUID v4
:基于随机数 + 版本号标准crypto.randomUUID()
(推荐)- 自增 ID + 时间戳(数据库主键)
Q15: 为什么 UUID v4 还可能重复?
A15: 理论概率极低(1/2^122),但 PRNG 质量差时风险上升 ❗
👉继续看?Q16: 如何实现一个全局唯一 ID 服务?
A16: Snowflake 算法:时间戳 + 机器 ID + 序列号,Twitter 开源方案 🌟
8. Webpack 自定义操作实战
- 自定义 loader:
.i18n
文件转多语言 JSON - plugin 注入构建时间、git commit hash
- splitChunks 优化:第三方库单独打包 + 长缓存
- 构建分析:
webpack-bundle-analyzer
js
// 自定义插件示例
class BuildInfoPlugin {
apply(compiler) {
compiler.hooks.done.tap('Info', () => {
require('fs').writeFileSync('build.json', JSON.stringify({
time: Date.now(),
commit: require('child_process').execSync('git rev-parse HEAD')
}));
});
}
}
⚠️插件 hook 选错了会导致内存泄漏?
9. 按钮点击顺序 aba,如何保证返回 aba?
js
// 问题本质:异步执行顺序控制
let result = [];
async function clickA() {
const data = await fetch('/api/a');
result.push('a');
}
async function clickB() {
const data = await fetch('/api/b');
result.push('b');
}
// 点击顺序:a -> b -> a
// 但网络延迟可能导致:b 先返回 → ['a','b','a'] ❌ → ['b','a','a']
✅ 解法:Promise 队列 + 序列号
js
let queue = Promise.resolve();
let seq = 0;
function request(url) {
const current = ++seq;
queue = queue.then(() => fetch(url));
return queue.then(res => {
if (current !== seq) throw new Error('过期请求');
return res;
});
}
👉继续看?Q17: 这叫什么模式?
A17: Promise Pipeline + 请求序号比对,RxJS switchMap 的反向逻辑 💥
(1) Node 接口转发优化
- 请求合并:多个用户查同一数据 → 缓存 + Promise 共享
- 超时熔断:
Promise.race([fetch, timeout])
- 降级策略:缓存兜底 or 默认值
(2) Node 服务稳定性
- PM2 集群模式 + 自动重启
- 健康检查 + Nginx 平滑摘流
- 日志监控 + Sentry 异常上报
- 限流:
express-rate-limit
- 优雅关闭:处理完现有请求再退出
10. Promise.all 实现原理
js
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
if (!promises.length) return resolve([]); // 空数组直接 resolve
const results = [];
let completed = 0;
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]) // 包装非 promise
.then(value => {
results[i] = value; // 保持顺序
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch(reject); // 一个失败就 reject
}
});
};
⚠️为什么用 Promise.resolve 包一层?
Q18: 如果传 [1,2,3] 会怎样?
A18: 正常返回 [1,2,3],因为 Promise.resolve(1) 直接 resolve 💡
Q19: Promise.allSettled 和 all 有什么区别?
A19: allSettled 等全部完成(fulfilled/rejected),不会中断 ✅