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

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax