扩展本机JavaScript数组

时间:2012-12-22 06:34:30

标签: javascript typescript

有没有办法从JS本机函数继承一个类?

例如,我有一个像这样的JS函数:

function Xarray()
{
    Array.apply(this, arguments);
    //some stuff for insert, add and remove notification
}
Xarray.prototype = new Array();

我尝试将其转换为Typescript但我失败了!!

export class Xarray implements Array {
}

编译器要求我定义所有Array接口属性。我知道如果我需要这个Xarray.prototype = new Array();,我必须在TS中扩展Array

如何在TS中扩展JS本机对象?

10 个答案:

答案 0 :(得分:37)

从TypeScript 1.6开始,您可以扩展数组类型,请参阅What's new in TypeScript

以下是一个例子:

class MyNewArray<T> extends Array<T> {
    getFirst() {
        return this[0];
    }
}

var myArray = new MyNewArray<string>();
myArray.push("First Element");
console.log(myArray.getFirst()); // "First Element"

如果您要向ES5或更低级别发射,请使用以下代码:

class MyNewArray<T> extends Array<T> {
    constructor(...items: T[]) {
        super(...items);
        Object.setPrototypeOf(this, MyNewArray.prototype);
    }

    getFirst() {
        return this[0];
    }
}

详细了解为何需要here

答案 1 :(得分:12)

我认为没有办法继承像Array这样的现有接口,

export class Xarray implements Array {

}

您应该创建一个函数并使用其原型继承它。 Typescript也会接受它类似于javascript。

function Xarray(...args: any[]): void; // required in TS 0.9.5
function Xarray()
{
    Array.apply(this, arguments);
   // some stuff for insert, add and remove notification
}
Xarray.prototype = new Array();

更新:对此进行了详细讨论,并在jqfaq.com为此提供了最佳解决方案。

//a dummy class it to inherite array.
class XArray {
    constructor() {
        Array.apply(this, arguments);   
        return new Array();
    }
    // we need this, or TS will show an error,
    //XArray["prototype"] = new Array(); will replace with native js arrray function
    pop(): any { return "" };
    push(val): number { return 0; };
    length: number;
}
//Adding Arrray to XArray prototype chain.
XArray["prototype"] = new Array();

//our Class
class YArray extends XArray {
///Some stuff
}

var arr = new YArray();
//we can use the array prop here.
arr.push("one");
arr.push("two");

document.writeln("First Elemet in array : " + arr[0]);
document.writeln("</br>Array Lenght : " + arr.length);

希望,这可能对你有所帮助!!!

答案 2 :(得分:8)

是的,可以在TS中扩展本机JS对象,但是存在扩展内置类型(lib.d.ts中包含的类型)的问题,例如Array。阅读此文章以获得解决方法:http://typescript.codeplex.com/workitem/4

因此,可以通过以下方式定义在稍后阶段扩展本机类型对象的类型接口:

/// <reference path="lib.d.ts"/>
interface Array {
    sort: (input: Array) => Array;
}

使用一个具体示例,您可以对数组中的某些元素进行排序,这些元素在接口中定义排序函数,然后在对象上实现它。

class Math implements Array {
    sort : (x: Array) => Array {
          // sorting the array
    }
}
var x = new Math();
x.sort([2,3,32,3]);

答案 3 :(得分:5)

在研究这个问题时,我偶然发现了Ben Nadel在Extending JavaScript Arrays While Keeping Native Bracket-Notation Functionality上的优秀帖子。在对如何将其成功转换为TypeScript的初步混淆之后,我创建了一个可以进行子类化的完全工作的Collection类。

它可以完成数组所能做的所有事情,包括用括号括起来,在循环结构中使用(for,while,forEach),map等。

主要实施点是

  1. 在构造函数中创建一个数组,将方法添加到数组并从构造函数
  2. 返回
  3. 复制Array方法的虚拟声明以传递implements Array
  4. 使用示例:

      var foo = new Foo({id : 1})
      var c = new Collection();
    
      c.add(foo)
      c.length === 1;    // => true
    
      foo === c[0];      // => true
      foo === c.find(1); // => true
    

    我做了available as a gist,完成了测试和子类的示例实现,但是我在这里提供了完整的源代码:

    /*
     * Utility "class" extending Array with lookup functions
     *
     * Typescript conversion of Ben Nadel's Collection class.
     * https://gist.github.com/fatso83/3773d4cb5f39128b3732
     *
     * @author Carl-Erik Kopseng
     * @author Ben Nadel (javascript original)
     */
    
    export interface Identifiable {
        getId : () => any;
    }
    
    export class Collection<T extends Identifiable> implements Array<T> {
    
        constructor(...initialItems:any[]) {
            var collection = Object.create(Array.prototype);
    
            Collection.init(collection, initialItems, Collection.prototype);
    
            return collection;
        }
    
        static init(collection, initialItems:any[], prototype) {
            Object.getOwnPropertyNames(prototype)
                .forEach((prop) => {
                    if (prop === 'constructor') return;
    
                    Object.defineProperty(collection, prop, { value: prototype[prop] })
                });
    
            // If we don't redefine the property, the length property is suddenly enumerable!
            // Failing to do this, this would fail: Object.keys([]) === Object.keys(new Collection() )
            Object.defineProperty(collection, 'length', {
                value: collection.length,
                writable: true,
                enumerable: false
            });
    
            var itemsToPush = initialItems;
            if (Array.isArray(initialItems[0]) && initialItems.length === 1) {
                itemsToPush = initialItems[0];
            }
            Array.prototype.push.apply(collection, itemsToPush);
    
            return collection;
        }
    
        // Find an element by checking each element's getId() method
        public find(id:any):T;
    
        // Find an element using a lookup function that
        // returns true when given the right element
        public find(lookupFn:(e:T) => boolean):T ;
    
        find(x:any) {
            var res, comparitor;
    
            if (typeof x === 'function') {
                comparitor = x;
            } else {
                comparitor = (e) => {
                    return e.getId() === x;
                }
            }
    
            res = [].filter.call(this, comparitor);
    
            if (res.length) return res[0];
            else return null;
        }
    
        // Add an element
        add(value:T);
    
        // Adds all ements in the array (flattens it)
        add(arr:T[]);
    
        add(arr:Collection<T>);
    
        add(value) {
    
            // Check to see if the item is an array or a subtype thereof
            if (value instanceof Array) {
    
                // Add each sub-item using default push() method.
                Array.prototype.push.apply(this, value);
    
            } else {
    
                // Use the default push() method.
                Array.prototype.push.call(this, value);
    
            }
    
            // Return this object reference for method chaining.
            return this;
    
        }
    
        remove(elem:T):boolean;
    
        remove(lookupFn:(e:T) => boolean):boolean ;
    
        remove(x:any):boolean {
            return !!this._remove(x);
        }
    
        /**
         * @return the removed element if found, else null
         */
        _remove(x:any):T {
            var arr = this;
            var index = -1;
    
            if (typeof x === 'function') {
    
                for (var i = 0, len = arr.length; i < len; i++) {
                    if (x(this[i])) {
                        index = i;
                        break;
                    }
                }
    
            } else {
                index = arr.indexOf(x);
            }
    
            if (index === -1) {
                return null;
            }
            else {
                var res = arr.splice(index, 1);
                return res.length ? res[0] : null;
            }
        }
    
    
        // dummy declarations
        // "massaged" the Array interface definitions in lib.d.ts to fit here
        toString:()=> string;
        toLocaleString:()=> string;
        concat:<U extends T[]>(...items:U[])=> T[];
        join:(separator?:string)=> string;
        pop:()=> T;
        push:(...items:T[])=> number;
        reverse:()=> T[];
        shift:()=> T;
        slice:(start?:number, end?:number)=> T[];
        sort:(compareFn?:(a:T, b:T) => number)=> T[];
        splice:(start?:number, deleteCount?:number, ...items:T[])=> T[];
        unshift:(...items:T[])=> number;
        indexOf:(searchElement:T, fromIndex?:number)=> number;
        lastIndexOf:(searchElement:T, fromIndex?:number)=> number;
        every:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean;
        some:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean;
        forEach:(callbackfn:(value:T, index:number, array:T[]) => void, thisArg?:any)=> void;
        map:<U>(callbackfn:(value:T, index:number, array:T[]) => U, thisArg?:any)=> U[];
        filter:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> T[];
        reduce:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
        reduceRight:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
        length:number;
    [n: number]: T;
    }
    

    当然,不需要Identifiablefindremove方法上的位,但我提供它们,因为完整的示例比使用更有用。一个没有任何方法的裸骨收藏。

答案 4 :(得分:3)

返回对象的构造函数隐式地将this的值替换为super()的调用者。生成的构造函数代码必须捕获super()返回的任何内容,并将其替换为this

内置类使用ES6 new.target来执行修复,但是ES5代码无法确保new.target具有调用构造函数的值。

这就是为什么你的额外方法消失了 - 你的对象有错误的原型。

您需要做的就是在调用super()后修复原型链。

export class RoleSet extends Array {
  constructor() {
    super();
    Object.setPrototypeOf(this, RoleSet.prototype);
  }
  private containsRoleset(roleset:RoleSet){
      if (this.length < roleset.length) return false;
      for (var i = 0; i < roleset.length; i++) {
        if (this.indexOf(roleset[i]) === -1) return false;
      }
      return true;
  }
  public contains(item: string | RoleSet): boolean {
    if (item) {
      return typeof item === "string" ? 
        this.indexOf(item) !== -1 : 
        this.containsRoleset(item);
    } else {
      return true;
    }
  }
}

要知道这个咒诅会折磨你的孩子和孩子的孩子,直到守则结束;你必须在每一代继承链中进行修复。

答案 5 :(得分:1)

如果你已经有一个有效的Xarray实现,我没有看到在typescript中重新创建它的重点,最终将编译回JavaScript。

但我确实看到了能够在TypeScript中使用Xarray的重点。

为了实现这一目标,您只需要Xarray的界面。您甚至不需要具体的接口实现,因为现有的js实现将作为一个实现。

interface Xarray{
    apply(...arguments : any[]) : void;
    //some stuff for insert, add and ...
}
declare var Xarray: {
   new (...items: any[]): Xarray;
   (...items: any[]): Xarray;
   prototype: Array; // This should expose all the Array stuff from ECMAScript 
}

执行此操作后,应该能够通过声明的变量使用自定义类型,而无需在TypeScript中实际实现它。

var xArr = new Xarray();
xArr.apply("blah", "hehe", "LOL");

您可以在此处查看参考资料,了解他们如何输入ECMAScript Array APIhttp://typescript.codeplex.com/SourceControl/changeset/view/2bee84410e02#bin/lib.d.ts

答案 6 :(得分:1)

在你的情况下,一个好的选择是使用这种模式:

function XArray(array) {
  array = array || [];

  //add a new method
  array.second = function second() {
    return array[1];
  };

  //overwrite an existing method with a super type pattern
  var _push = array.push;
  array.push = function push() {
    _push.apply(array, arguments);
    console.log("pushed: ", arguments);
  };

  //The important line.
  return array
}

然后你可以这样做:

var list = XArray([3, 4]);
list.second()   ; => 4

list[1] = 5;
list.second()   ; => 5
但请注意:

list.constructor  ; => Array and not XArray

答案 7 :(得分:1)

是的,您可以增加内置类型,并且不会像其他答案中所描述的那样需要XArray的所有设备,并且更接近于如何在javascript中执行此操作。

Typescript允许多种方法来实现这一点,但是对于像Array和Number这样的内置类型,你需要使用&#34;合并&#34;并声明全局命名空间以扩充类型,请参阅the docs

因此对于Array,我们可以添加一个可选的元数据对象和一个获取第一个成员

declare global {
  interface Array<T> {
    meta?: any|null ,
    getFirst(): T
  }
}

if(!Array.prototype.meta )
{
  Array.prototype.meta = null
}
if(!Array.prototype.getFirst )
{
  Array.prototype.getFirst = function() {
    return this[0];
  }
}

我们可以这样使用:

let myarray: number[] = [ 1,2,3 ]
myarray.meta = { desc: "first three ints" }
let first: number = myarray.getFirst()

同样适用于Number我想添加一个模数函数,它不受限于余数%

declare global {
  interface Number {
    mod(n:number): number
  }
}

if(!Number.prototype.mod )
{
  Number.prototype.mod = function (n: number) {
          return ((this % n) + n) % n;
  }
}

我们可以像这样使用它:

let foo = 9;
console.log("-9.mod(5) is "+ foo.mod(5))    

对于我们可能想要添加API的函数,即使其行为像函数和对象,我们可以使用hybrid types (see docs)

// augment a (number) => string  function with an API
interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

//helper function to get my augmented counter function with preset values
function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

像这样使用它: -

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

答案 8 :(得分:0)

为了克服原生Array类扩展的问题,我利用了一个装饰器。

&#13;
&#13;
function extendArray(constructor: Function) {
    Object.getOwnPropertyNames(constructor.prototype)
        .filter(name => name !== 'constructor')
.forEach(name => {
    const attributes = Object.getOwnPropertyDescriptor(constructor.prototype, name);
    Object.defineProperty(Array.prototype, name, attributes);
  });
}

@extendArray
export class Collection<T> extends Array<T> {
  constructor(...args: T[]) {
    super(...args);
  }
  // my appended methods
}
&#13;
&#13;
&#13;

BTW如果要使用装饰工厂,这个装饰器可以更通用(对于其他本机类)。

答案 9 :(得分:-2)

不知道这有多皱眉,但是例如我需要创建一个BulletTypes数组,以便我可以循环使用它们。我做的是以下内容:

interface BulletTypesArray extends Array<BulletType> {
    DefaultBullet?: Bullet; 
}

var BulletTypes: BulletTypesArray = [ GreenBullet, RedBullet ];
BulletTypes.DefaultBullet = GreenBullet;

显然你也可以创建一个通用界面,比如interface SuperArray<T> extends Array<T>