Extend native JavaScript array

后端 未结 10 869
情书的邮戳
情书的邮戳 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: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 implements Array {
    
        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);
    
        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:(...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:(callbackfn:(value:T, index:number, array:T[]) => U, thisArg?:any)=> U[];
        filter:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> T[];
        reduce:(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
        reduceRight:(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.

提交回复
热议问题