You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中能否继承ReadonlyArray/ReadonlySet/ReadonlyMap?

实现TypeScript自定义只读行向量类(ReadonlyRowVector)

问题背景

在TypeScript中尝试直接继承ReadonlyArray会报错,因为它是接口而非类:

export class ReadonlyRowVector<ElementType> extends ReadonlyArray<ElementType> { /* ... */ }

TS2689: Cannot extend an interface ReadonlyArray. Did you mean implements?

若改为继承Array并实现ReadonlyArray,则会保留push/pop等可变方法,不符合只读需求;直接类型转换也会因泛型作用域问题报错。我们需要一个兼具只读特性、自定义方法,且支持instanceof校验的行向量类。

解决方案:组合模式实现

通过内部维护只读数组+实现ReadonlyArray接口的方式,既保证类型兼容,又完全控制暴露的方法,同时支持instanceof校验。

完整实现代码

import {
  isNaturalNumber,
  Logger,
  InvalidParameterValueError,
  isNumber
} from "@yamato-daiwa/es-extensions";

export default class ReadonlyRowVector<ElementType> implements ReadonlyArray<ElementType> {
  // 内部存储只读数据
  private readonly elements: ReadonlyArray<ElementType>;

  // 实现ReadonlyArray核心属性与方法
  get length(): number {
    return this.elements.length;
  }

  [index: number]: ElementType;

  public entries(): IterableIterator<[number, ElementType]> {
    return this.elements.entries();
  }

  public keys(): IterableIterator<number> {
    return this.elements.keys();
  }

  public values(): IterableIterator<ElementType> {
    return this.elements.values();
  }

  public forEach(callbackfn: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => void, thisArg?: any): void {
    this.elements.forEach(callbackfn, thisArg);
  }

  public every(callbackfn: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => unknown, thisArg?: any): boolean {
    return this.elements.every(callbackfn, thisArg);
  }

  public some(callbackfn: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => unknown, thisArg?: any): boolean {
    return this.elements.some(callbackfn, thisArg);
  }

  public find<S extends ElementType>(predicate: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => value is S, thisArg?: any): S | undefined {
    return this.elements.find(predicate, thisArg);
  }

  public findIndex(predicate: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => unknown, thisArg?: any): number {
    return this.elements.findIndex(predicate, thisArg);
  }

  public includes(searchElement: ElementType, fromIndex?: number): boolean {
    return this.elements.includes(searchElement, fromIndex);
  }

  public indexOf(searchElement: ElementType, fromIndex?: number): number {
    return this.elements.indexOf(searchElement, fromIndex);
  }

  public lastIndexOf(searchElement: ElementType, fromIndex?: number): number {
    return this.elements.lastIndexOf(searchElement, fromIndex);
  }

  public join(separator?: string): string {
    return this.elements.join(separator);
  }

  public slice(start?: number, end?: number): ReadonlyArray<ElementType> {
    return this.elements.slice(start, end);
  }

  public map<U>(callbackfn: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => U, thisArg?: any): U[] {
    return this.elements.map(callbackfn, thisArg);
  }

  public filter<S extends ElementType>(predicate: (value: ElementType, index: number, array: ReadonlyArray<ElementType>) => value is S, thisArg?: any): S[] {
    return this.elements.filter(predicate, thisArg);
  }

  public reduce<U>(callbackfn: (previousValue: U, currentValue: ElementType, currentIndex: number, array: ReadonlyArray<ElementType>) => U, initialValue: U): U {
    return this.elements.reduce(callbackfn, initialValue);
  }

  public reduceRight<U>(callbackfn: (previousValue: U, currentValue: ElementType, currentIndex: number, array: ReadonlyArray<ElementType>) => U, initialValue: U): U {
    return this.elements.reduceRight(callbackfn, initialValue);
  }

  // 自定义静态工厂方法
  public static fromArray<ElementType>(array: ReadonlyArray<ElementType>): ReadonlyRowVector<ElementType> {
    return new ReadonlyRowVector(...array);
  }

  constructor(...elements: ReadonlyArray<ElementType>) {
    this.elements = [...elements];
    // 实现数组式迭代
    Object.defineProperty(this, Symbol.iterator, {
      value: () => this.elements[Symbol.iterator]()
    });
    // 实现索引访问
    elements.forEach((elem, idx) => {
      Object.defineProperty(this, idx, {
        get: () => this.elements[idx],
        enumerable: true
      });
    });
  }

  // 自定义从1开始计数的元素获取方法
  public getElementAt__numerationFrom1(targetElementNumber__numerationFrom1: number): ElementType {
    if (!isNaturalNumber(targetElementNumber__numerationFrom1)) {
      Logger.throwErrorAndLog({
        errorInstance: new InvalidParameterValueError({
          parameterNumber: 1,
          parameterName: "targetElementNumber__numerationFrom1",
          messageSpecificPart: [
            "目标元素序号必须是自然数",
            ...isNumber(targetElementNumber__numerationFrom1) ?
                `但当前值为 ${targetElementNumber__numerationFrom1},并非自然数。` :
                `但当前类型为 "${typeof targetElementNumber__numerationFrom1}"。`
          ].join(" ")
        }),
        title: InvalidParameterValueError.localization.defaultTitle,
        occurrenceLocation: "RowVector.getElementAt__numerationFrom1(targetElementNumber__numerationFrom1)"
      });
    }

    if (targetElementNumber__numerationFrom1 > this.length) {
      Logger.throwErrorAndLog({
        errorInstance: new InvalidParameterValueError({
          parameterNumber: 1,
          parameterName: "targetElementNumber__numerationFrom1",
          messageSpecificPart:
              `请求的元素序号为 ${targetElementNumber__numerationFrom1}(从1开始计数),但目标行向量仅包含 ${this.length} 个元素。`
        }),
        title: InvalidParameterValueError.localization.defaultTitle,
        occurrenceLocation: "RowVector.getElementAt__numerationFrom1(targetElementNumber__numerationFrom1)"
      });
    }

    return this.elements[targetElementNumber__numerationFrom1 - 1];
  }
}

方案优势

  1. 完全只读:内部数据通过private readonly保证不可变,不会暴露Array的可变方法(如push/pop)。
  2. 类型兼容:实现ReadonlyArray接口,可直接作为只读数组使用,支持所有只读数组的操作。
  3. instanceof校验:作为类,支持instanceof ReadonlyRowVector和instanceof ReadonlyColumnVector这类线性代数场景下的类型校验。
  4. 自定义扩展:可以自由添加fromArray、getElementAt__numerationFrom1等业务相关方法。

内容的提问来源于stack exchange,提问作者Takeshi Tokugawa YD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:14:51