首页 > 解决方案 > TypeScript:扩展原生类会给出不正确的原型

问题描述

问题

在 TypeScript 中,当我这样做时:

class B { ... }
class A extends B { ... }

我可以正常调用原型 A 的方法/获取器/设置器,而不会出错。

但是,当我对原生类做同样的事情时:

class A extends Array { ... }

当我调用原型 A 的方法/获取器/设置器时,它会抛出,因为原型 A 中的属性都是未定义的。

为什么?

例子

鉴于此工作代码:

class Animal {

  protected name: string;
  protected age: number;

  constructor(name: string, age: number){
    this.name = name;
  }

  eat(){
    console.log(`${this.name} has eaten.`);
  }

  sleep(){
    console.log(`${this.name} has slept.`);
  }

}

class Dog extends Animal {

  constructor(...args: [string, number]){
    super(...args);
  }

  bark(){
    console.log(`${this.name} has barked.`);
  }

}

const dog = new Dog('doggo', 2);
dog.eat();
dog.bark();

它将被转译成:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Animal = /** @class */ (function () {
    function Animal(name, age) {
        this.name = name;
    }
    Animal.prototype.eat = function () {
        console.log(this.name + " has eaten.");
    };
    Animal.prototype.sleep = function () {
        console.log(this.name + " has slept.");
    };
    return Animal;
}());
var Dog = /** @class */ (function (_super) {
    __extends(Dog, _super);
    function Dog() {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        return _super.apply(this, args) || this;
    }
    Dog.prototype.bark = function () {
        console.log(this.name + " has barked.");
    };
    return Dog;
}(Animal));
var dog = new Dog('doggo', 2);
dog.eat();
dog.bark();

到目前为止,一切都很好。

现在,当我尝试扩展本机类(在本例中为Array)时:

class List<T> extends Array<T> {

  private index: number;

  constructor(...items: T[]) {
    super(...items);
    Object.defineProperty(this, 'index', {
      value: 0,
      writable: true,
      configurable: true
    });
  }

  get current() {
    return this[this.index];
  }

  get max() {
    return this.length - 1;
  }

  next() {
    return this.index === this.max
      ? this.first()
      : this[++this.index];
  }

  prev() {
    return this.index === 0
      ? this.last()
      : this[--this.index];
  }

  first() {
    return this[this.index = 0];
  }

  last() {
    return this[this.index = this.max];
  }

}

const list = new List(1, 2, 3);
console.log('List:', list);
console.log('Length:', list.length);
console.log('Current value:', list.current);
console.log('Next value:', list.next());

它被转译成以下代码:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var List = /** @class */ (function (_super) {
    __extends(List, _super);
    function List() {
        var items = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            items[_i] = arguments[_i];
        }
        var _this = _super.apply(this, items) || this;
        Object.defineProperty(_this, 'index', {
            value: 0,
            writable: true,
            configurable: true
        });
        return _this;
    }
    Object.defineProperty(List.prototype, "current", {
        get: function () {
            return this[this.index];
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(List.prototype, "max", {
        get: function () {
            return this.length - 1;
        },
        enumerable: true,
        configurable: true
    });
    List.prototype.next = function () {
        return this.index === this.max
            ? this.first()
            : this[++this.index];
    };
    List.prototype.prev = function () {
        return this.index === 0
            ? this.last()
            : this[--this.index];
    };
    List.prototype.first = function () {
        return this[this.index = 0];
    };
    List.prototype.last = function () {
        return this[this.index = this.max];
    };
    return List;
}(Array));
var list = new List(1, 2, 3);
console.log('List:', list);
console.log('Length:', list.length);
console.log('Current value:', list.current);
console.log('Next value:', list.next());

为什么?

标签: typescript

解决方案


我看到你的目标是 ES5。转译到 ESNext 时,您的代码将按预期完美运行。如果您需要以 ES5 为目标并扩展原生类型(例如Array),则必须在构造函数中设置您自己的类的原型(Object.setPrototypeOf)。

class List<T> extends Array<T> {

  constructor(...items: T[]) {
    super(...items);
    // ...
    Object.setPrototypeOf(this, Object.create(List.prototype)); // (Object as any).setPrototypeOf
  }

  // ...

}

检查 操场示例中的控制台。

还要检查另一个有用的链接,了解如何扩展原生类型,如Array.


推荐阅读