Extend native JavaScript array

后端 未结 10 806
情书的邮戳
情书的邮戳 2020-11-27 22:08

Is there any way to inherit a class from JS native function?

For example, I have a JS function like this:

function Xarray()
{
    Array.apply(this, a         


        
相关标签:
10条回答
  • 2020-11-27 22:31

    Yes it's possible to extend a native JS object in TS, however there is an issue extending built-in types (those included in lib.d.ts) like Array. Read this post for workaround: http://typescript.codeplex.com/workitem/4

    So defining a type interface which extends a native type object at a later stage can be done in the following way:

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

    Using on a concrete example, you can sort some elements on an array which define a sort function in an interface and later implements it on an object.

    class Math implements Array {
        sort : (x: Array) => Array {
              // sorting the array
        }
    }
    var x = new Math();
    x.sort([2,3,32,3]);
    
    0 讨论(0)
  • 2020-11-27 22:32

    Starting in TypeScript 1.6, you can extend the Array type, see What's new in TypeScript

    Here's an example:

    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"
    

    If you are emitting to ES5 or below, then use the following code:

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

    Read more about why this is necessary here.

    0 讨论(0)
  • 2020-11-27 22:42

    I don't think there is a way to inherit existing interfaces like Array,

    export class Xarray implements Array {
    
    }
    

    You should create a function and inherit it with its prototype. Typescript also will accept it which is similar to 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();
    

    UPDATE: This one is discussed well and provided the best solution for this at 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);
    

    Hope, this might help you!!!

    0 讨论(0)
  • 2020-11-27 22:48

    While researching this, I came across Ben Nadel's excellent post on Extending JavaScript Arrays While Keeping Native Bracket-Notation Functionality. After some initial confusion on how to succesfully convert this into TypeScript, I created a fully working Collection class that can be subclassed.

    It can do everything an Array can, including indexing by brackets,use in loop constructions (for, while, forEach), maps, etc.

    The main implementation points are

    1. Create an array in the constructor, add the methods to the array and return that from the constructor
    2. Copy dummy declarations of Array methods to pass the implements Array bit

    Example of usage:

      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
    

    I made it available as a gist, complete with tests and an example implementation of a subclass, but I present the full source here:

    /*
     * 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;
    }
    

    Of course, the bits on Identifiable, the find and remove methods are not needed, but I supply them none the less as a full fledged example is a tad more usable than a bare-bones Collection without any methods of its own.

    0 讨论(0)
  • 2020-11-27 22:48

    Yes you can augment the Builtin types and do it in a way that doesn't require all the paraphernalia of an XArray as described in the other answers and is closer to how you would do it in javascript.

    Typescript allows a number of ways to do this, but for the Builtin types like Array and Number you need to use "merging" and declare the global namespace to augment the types, see the docs

    so for Array we can add an optional metadata object and a get first member

    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];
      }
    }
    

    we can use this like so:

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

    The same goes for Number say I want to add a modulo function that isn't limited like the remainder %

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

    and we can use it like so:

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

    For Functions that we may want to add an API to ie to make it behave like a function and an object we can use 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;
    }
    

    use it like so:-

    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;
    
    0 讨论(0)
  • 2020-11-27 22:50

    If you already have a working Xarray implementation, I don't see the point in recreating it in typescript, which eventually will compile back to JavaScript.

    But I do see the point in being able to use the Xarray in TypeScript.

    In order to accomplish this, you simply need an interface for your Xarray. You don't even need to have a concrete implementation of your interface since your existing js implementation will serve as one.

    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 
    }
    

    After doing this, should be able to use your custom defined type through the declared variable without actually implementing it in TypeScript.

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

    You might look for reference here to see how they typed the ECMAScript Array API: http://typescript.codeplex.com/SourceControl/changeset/view/2bee84410e02#bin/lib.d.ts

    0 讨论(0)
提交回复
热议问题