🔥9种继承写法全解,第7种99%人没用过?⚠️

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' }
  ]
};
graph LR VNode[虚拟DOM] --> Diff[Diff算法] Diff --> Patch[打补丁] Patch --> RealDOM[真实DOM]

⚠️vdom 真的比直接操作 dom 快吗?

Q11: 什么时候 vdom 性能更差?

A11: 单次简单操作(如改一个文本),直接 dom 更快 ❌

Q12: React 和 Vue 的 diff 策略有何不同?

A12: React 单节点比对 + key 优化;Vue 双端比对 + 预判优化 💡


5. 304 是什么?

HTTP 状态码 304 Not Modified,协商缓存生效标志

流程:

sequenceDiagram 浏览器->>服务器: GET /a.js (If-None-Match: abc123) 服务器-->>浏览器: 304 (无响应体,用本地缓存)

⚠️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),不会中断 ✅

相关推荐
隔壁老王z4 分钟前
设计实现一个Web 终端:基于 Vue 3 和 Xterm.js 的实践
前端·iterm
中微子5 分钟前
简单介绍跨域资源共享(CORS)
前端
極光未晚9 分钟前
Vue 项目 webpack 打包体积分析:从 “盲猜优化” 到 “精准瘦身”
前端·vue.js·性能优化
刘小筛15 分钟前
Ant Design Vue (2x) 按钮(button)单击后离开,按钮状态无变化
前端
mogullzr18 分钟前
4.1.ByteOJ用户模块——登录注册功能(RSA + TimeStamp加密过)
前端·后端
鹏多多.19 分钟前
flutter-使用AnimatedDefaultTextStyle实现文本动画
android·前端·css·flutter·ios·html5·web
卑微前端在线挨打1 小时前
2025数字马力一面面经(社)
前端
OpenTiny社区1 小时前
一文解读“Performance面板”前端性能优化工具基础用法!
前端·性能优化·opentiny
拾光拾趣录2 小时前
🔥FormData+Ajax组合拳,居然现在还用这种原始方式?💥
前端·面试
不会笑的卡哇伊2 小时前
新手必看!帮你踩坑h5的微信生态~
前端·javascript