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

相关推荐
BBB努力学习程序设计14 小时前
CSS3渐变:用代码描绘色彩的流动之美
前端·html
冰暮流星15 小时前
css之动画
前端·css
jump68015 小时前
axios
前端
进击的野人15 小时前
CSS选择器与层叠机制
css·面试
spionbo15 小时前
前端解构赋值避坑指南基础到高阶深度解析技巧
前端
用户40993225021215 小时前
Vue响应式声明的API差异、底层原理与常见陷阱你都搞懂了吗
前端·ai编程·trae
开发者小天15 小时前
React中的componentWillUnmount 使用
前端·javascript·vue.js·react.js
永远的个初学者16 小时前
图片优化 上传图片压缩 npm包支持vue(react)框架开源插件 支持在线与本地
前端·vue.js·react.js
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ16 小时前
npm i / npm install 卡死不动解决方法
前端·npm·node.js
Kratzdisteln16 小时前
【Cursor _RubicsCube Diary 1】Node.js;npm;Vite
前端·npm·node.js