普通视图

发现新文章,点击刷新页面。
昨天 — 2025年8月13日首页

全面解析 JavaScript 类继承:方式、优缺点与应用场景

作者 excel
2025年8月13日 22:00

在 JavaScript 中,“继承”指的是让一个对象或类能够复用另一个对象或类的属性和方法。
由于 JavaScript 的面向对象机制是基于 原型(prototype) 而非传统类,因此继承方式丰富多样,从 ES5 时代的手动原型链,到 ES6 之后的 class 语法糖,都有不同的实现手段。

本文将系统介绍 8 种主要继承方式,分析它们的优缺点适用场景,并提供代码示例。


1. class 继承(ES6 extends

class Parent {
  greet() { console.log("Hello from Parent"); }
}
class Child extends Parent {
  greet() { super.greet(); console.log("...and from Child"); }
}
new Child().greet();

优点

  • 语法简洁,接近 Java / C# 等语言。
  • 内置 super 调用父类构造和方法。
  • 支持继承内置对象(Array, Error 等)。
  • 性能接近最佳(底层是寄生组合继承)。

缺点

  • 只是语法糖,本质仍是原型链。
  • 高级场景(动态改继承结构)需理解原型机制。

适用场景

  • 现代前端、Node.js 项目的默认选择。

2. class + 表达式继承(动态继承)

function mixin(base) {
  return class extends base {
    extra() { console.log("extra"); }
  }
}
class Parent {}
class Child extends mixin(Parent) {}
new Child().extra();

优点

  • 运行时动态决定父类。
  • 易于组合多个基类。

缺点

  • 可读性差,调试困难。

适用场景

  • 插件系统、UI 组件动态扩展。

3. 原型链继承

function Parent() { this.colors = ["red", "blue"]; }
Parent.prototype.say = function() { console.log("parent"); };

function Child() {}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

优点

  • 实现简单。
  • 子类可直接访问父类原型方法。

缺点

  • 属性被所有实例共享(引用类型容易出错)。
  • 无法向父类构造传参。

适用场景

  • 学习原型链原理,已不推荐在生产使用。

4. 借用构造函数继承

function Parent(name) { this.name = name; }
function Child(name) { Parent.call(this, name); }

优点

  • 每个实例的属性独立。
  • 可传参。

缺点

  • 无法继承父类原型方法,方法需重复定义。

适用场景

  • 仅继承属性,不需要父类方法的简单对象。

5. 组合继承

function Parent(name) { this.name = name; }
Parent.prototype.say = function() { console.log(this.name); };

function Child(name) {
  Parent.call(this, name); // 继承属性
}
Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child;

优点

  • 继承父类属性与方法。
  • 子类实例独立。

缺点

  • 父类构造函数被调用两次。

适用场景

  • ES5 最常用方式,简单可靠。

6. 寄生组合继承

function inherit(Child, Parent) {
  Child.prototype = Object.create(Parent.prototype);
  Child.prototype.constructor = Child;
}
function Parent(name) { this.name = name; }
Parent.prototype.say = function() { console.log(this.name); };

function Child(name) {
  Parent.call(this, name);
}
inherit(Child, Parent);

优点

  • 父类构造只调用一次,性能好。
  • 完整继承属性和方法。

缺点

  • 写法比组合继承稍复杂。

适用场景

  • ES5 推荐的最佳继承方案。

7. 原型式继承

let parent = { greet() { console.log("hello"); } };
let child = Object.create(parent);
child.greet();

优点

  • 极简,无需构造函数。
  • 灵活,适合快速克隆对象。

缺点

  • 属性共享,引用类型有风险。
  • 无法传参初始化。

适用场景

  • 创建配置模板、数据对象。

8. 寄生式继承

function createChild(o) {
  let clone = Object.create(o);
  clone.say = function() { console.log("hi"); };
  return clone;
}
let parent = { greet() { console.log("hello"); } };
let child = createChild(parent);
child.say();

优点

  • 在原型式继承基础上增强对象。
  • 灵活度高。

缺点

  • 方法无法复用,浪费内存。

适用场景

  • 一次性对象增强。

9. Mixin 混入

const sayMixin = { say() { console.log("hi"); } };
class Person {}
Object.assign(Person.prototype, sayMixin);

优点

  • 模拟多继承。
  • 灵活扩展功能。

缺点

  • 命名冲突风险。
  • 方法来源分散,不易维护。

适用场景

  • 事件系统、功能扩展。

继承方式对比表

方式 优点 缺点 推荐度 场景
class 继承 语法简洁,支持 super 语法糖,本质是原型链 ⭐⭐⭐⭐⭐ 现代项目
class + 表达式 动态父类 可读性差 ⭐⭐⭐ 插件系统
原型链继承 简单直观 属性共享,不能传参 学习原理
借用构造函数 属性独立,可传参 不继承方法 ⭐⭐ 仅需属性
组合继承 属性独立+继承方法 调用两次父构造 ⭐⭐⭐⭐ ES5 常用
寄生组合继承 性能最佳 写法稍复杂 ⭐⭐⭐⭐⭐ ES5 推荐
原型式继承 极简 属性共享 ⭐⭐ 对象克隆
寄生式继承 灵活增强 无法复用方法 ⭐⭐ 一次性增强
Mixin 多继承效果 命名冲突风险 ⭐⭐⭐ 功能扩展

结语

  • 如果是 现代项目:优先使用 class extends
  • 如果是 ES5 项目:用 寄生组合继承
  • 如果只是克隆/增强对象:用 Object.createMixin
  • 学习原型链原理时,可以用最简单的原型链继承练习。

理解继承方式,不仅是写代码的技巧,更是掌握 JavaScript 对象模型的关键。

昨天以前首页

JavaScript 中的二进制数据:ArrayBuffer 与 SharedArrayBuffer 全面解析

作者 excel
2025年8月12日 22:16

1. ArrayBuffer

  • 是什么:一段固定长度、连续的二进制内存。

  • 不能直接读写,必须通过视图(TypedArrayDataView)访问。

  • 常用场景

    • 文件读写(图片、音视频)
    • 网络传输(WebSocket、Fetch)
    • WebGL / GPU 数据
    • 加密解密数据
  • 示例

    const buf = new ArrayBuffer(4);
    const u8 = new Uint8Array(buf);
    u8[0] = 255;
    console.log(u8[0]); // 255
    

2. SharedArrayBuffer

  • 是什么:可以被多个线程同时访问的二进制内存。

  • 不会复制,跨线程传递时是零拷贝。

  • 需要配合 Atomics 保证数据同步。

  • 浏览器限制:需跨源隔离(COOP/COEP)。

  • 常用场景

    • 多线程数据共享(主线程 + Worker)
    • 实时游戏(物理计算 + 渲染)
    • 音视频解码与播放共享缓冲
    • 科学计算、机器学习
  • 示例

    const sab = new SharedArrayBuffer(4);
    const u32 = new Uint32Array(sab);
    u32[0] = 123;
    // 传给 Worker 后,两个线程可同时访问并修改
    

3. TypedArray

  • 是什么:定型数组视图,用固定类型访问 ArrayBuffer / SharedArrayBuffer

  • 特点

    • 高性能批量数值运算
    • 必须按类型大小对齐(如 Int32 按 4 字节)
    • 按系统字节序读写
  • 常用类型Uint8Array, Int16Array, Float32Array

  • 示例

    const arr = new Uint8Array([1, 2, 3]); // 自动分配底层 ArrayBuffer
    

4. DataView

  • 是什么:灵活的二进制视图,可按任意偏移读写各种类型,并指定字节序。

  • 特点

    • 不要求按类型大小对齐
    • 可选大端/小端序
    • 适合自定义二进制格式解析
  • 示例

    const buf = new ArrayBuffer(4);
    const dv = new DataView(buf);
    dv.setUint16(0, 65535, true); // 小端序
    console.log(dv.getUint16(0, true)); // 65535
    

5. 对比表

特性 ArrayBuffer SharedArrayBuffer TypedArray DataView
是否共享内存 ❌ 否 ✅ 是 依赖底层缓冲区 依赖底层缓冲区
跨线程传递 转移(原失效) 保留引用 依赖底层缓冲区 依赖底层缓冲区
类型 固定类型 任意类型
字节序控制 按系统字节序 ✅ 可指定
是否对齐 N/A N/A 必须对齐 不需要
常见用途 文件、网络、GPU 数据 多线程并发 数值计算 文件解析

ChatGPT Image 2025年8月12日 22_09_28.png

  • ArrayBuffer(蓝色块)

    • 位于中间,是原始二进制内存块,不能直接读写。
    • 需要通过视图(上方两个白色块)来访问。
  • TypedArray(左上白色块)

    • 一类固定类型、定长的数组视图(如 Uint8Array, Float32Array)。
    • 按系统字节序访问,速度快,适合批量数值计算。
    • 依赖底层的 ArrayBuffer 存储数据。
  • DataView(右上白色块)

    • 更通用的视图,可以按任意偏移读取各种类型,并指定大小端字节序。
    • 灵活度高,适合解析自定义二进制格式。
    • 也依赖底层的 ArrayBuffer
  • SharedArrayBuffer(下方蓝色块)

    • ArrayBuffer 类似,但可以在多个线程之间共享同一块内存(零拷贝)。
    • 需要配合 Atomics 来保证多线程数据安全。
    • 也能被 TypedArrayDataView 作为底层缓冲区使用。

📌 解读

  • ArrayBuffer / SharedArrayBuffer内存本体,负责存放原始数据。
  • TypedArray / DataView访问工具,决定如何解释和读写这块内存。
  • SharedArrayBuffer 可以理解为“多线程版 ArrayBuffer”,功能更强,但需要同步控制。

SharedArrayBuffer 使用限制及注意事项

SharedArrayBuffer 是 JavaScript 中用于在多个线程(如 Web Worker)间共享内存的底层对象,允许高效的并发数据访问。但它的使用受到多方面严格限制,主要出于安全和兼容性考虑:

  1. 跨源隔离要求(COOP + COEP)
    为防止安全漏洞(如 Spectre 侧信道攻击),现代浏览器要求页面必须开启跨源隔离才能启用 SharedArrayBuffer。
    具体来说,服务器必须设置以下 HTTP 响应头:

    • Cross-Origin-Opener-Policy: same-origin
    • Cross-Origin-Embedder-Policy: require-corp
      只有满足这两个条件,浏览器才允许页面访问 SharedArrayBuffer,否则会被禁用。
  2. 浏览器兼容性
    并非所有浏览器或版本都支持 SharedArrayBuffer。尤其是旧版浏览器或某些隐私模式下,可能完全禁用该功能。开发时应先检测是否支持。

  3. 固定大小且不可变
    SharedArrayBuffer 一旦创建,内存大小就固定了,无法动态扩展,使用时需要提前规划好内存大小。

  4. 必须通过视图访问
    SharedArrayBuffer 本身是底层原始内存块,不能直接读写,必须通过 TypedArray(如 Uint8Array)或 DataView 来访问。

  5. 多线程同步与安全
    由于 SharedArrayBuffer 允许多个线程共享内存,必须使用 Atomics 对象提供的原子操作来同步访问,避免竞态条件和数据损坏。

  6. 安全风险与性能考虑
    该技术虽高效,但使用不当容易引入竞态错误和安全隐患,建议仅在确有多线程共享内存需求且理解同步机制时使用。

❌
❌