109

I have to dynamically fetch the properties and functions of a ES6 class. Is this even possible?

Using a for...in loop, I only get to loop through the properties of a class instance:

class Foo {
  constructor() {
    this.bar = "hi";
  }
  someFunc() {
    console.log(this.bar);
  }
}
var foo = new Foo();
for (var idx in foo) {
  console.log(idx);
}

Output:

bar
Bruno Grieder
  • 28,128
  • 8
  • 69
  • 101
Jan
  • 1,268
  • 4
  • 12
  • 20

5 Answers5

90

The members of a class are not enumerable. To get them, you have to use Object.getOwnPropertyNames:

var propertyNames = Object.getOwnPropertyNames(Object.getPrototypeOf(foo));
// or
var propertyNames = Object.getOwnPropertyNames(Foo.prototype);

Of course this won't get inherited methods. There is no method that can give you all of them. You'd have to traverse the prototype chain and get the properties for each prototype individually.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
81

This function will get all functions. Inherited or not, enumerable or not. All functions are included.

function getAllFuncs(toCheck) {
    const props = [];
    let obj = toCheck;
    do {
        props.push(...Object.getOwnPropertyNames(obj));
    } while (obj = Object.getPrototypeOf(obj));
    
    return props.sort().filter((e, i, arr) => { 
       if (e!=arr[i+1] && typeof toCheck[e] == 'function') return true;
    });
}

Do test

getAllFuncs([1,3]);

console output:

["constructor", "toString", "toLocaleString", "join", "pop", "push", "concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", "entries", "keys", "constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__"]

Note

It doesn't return functions defined via symbols;

TOPKAT
  • 6,667
  • 2
  • 44
  • 72
Muhammad Umer
  • 17,263
  • 19
  • 97
  • 168
  • 1
    You could probably stop at `Object.prototype` if we really only want to get `class` methods. Otherwise, nice :) – Felix Kling Jun 25 '15 at 16:02
  • True, that would be useful in lots of conditions too. – Muhammad Umer Jun 25 '15 at 16:03
  • 3
    You'll also need to `.concat(Object.getOwnPropertySymbols(obj))` since `getOwnPropertyNames` will only return `string` keys. That means your example won't pick up an iterator function for example. – loganfsmyth Jun 25 '15 at 16:21
  • 2
    Nice solution. If you want to remove builtin things like `__defineGetter__` you can do `while ((obj = Object.getPrototypeOf(obj)) && obj != Object.prototype)` – antitoxic Aug 27 '16 at 18:27
  • 5
    Nice, but `obj` inside the filter is `null`. If it wasn't the `while` will never exit, right :) – AlexV Mar 17 '17 at 00:23
  • 1
    Yes, when obj becomes undefined while loop stops – Muhammad Umer Mar 17 '17 at 01:12
  • Don't miss out the extra brackets in @antitoxic's suggestion... otherwise you start getting the prototype of `true`, which is apparently not null, so you go back to `true` again the next iteration... – Jules Jul 03 '17 at 21:22
  • How can this work? When the loop exits, `obj` is falsy, so your filter will always fail because of the `typeof obj[e] == 'function'` reference. You need to restore the original value of `obj` after the loop. Actual sample output: "TypeError: Cannot read property '__defineGetter__' of null" – Vectorjohn Jan 14 '20 at 23:45
  • I'm not sure what I understand, if obj has prop a which is a function then obj.a will be true. – Muhammad Umer Jan 15 '20 at 13:04
46

ES6 adds Reflection which makes the code to do this a bit cleaner.

function getAllMethodNames(obj) {
  let methods = new Set();
  while (obj = Reflect.getPrototypeOf(obj)) {
    let keys = Reflect.ownKeys(obj)
    keys.forEach((k) => methods.add(k));
  }
  return methods;
}


/// a simple class hierarchy to test getAllMethodNames


// kind of like an abstract base class
class Shape {
  constructor() {}
  area() {
    throw new Error("can't define area for generic shape, use a subclass")
  }
}

// Square: a shape with a sideLength property, an area function and getSideLength function
class Square extends Shape {
  constructor(sideLength) {
    super();
    this.sideLength = sideLength;
  }
  area() {
    return this.sideLength * this.sideLength
  };
  getSideLength() {
    return this.sideLength
  };
}

// ColoredSquare: a square with a color
class ColoredSquare extends Square {
  constructor(sideLength, color) {
    super(sideLength);
    this.color = color;
  }
  getColor() {
    return this.color
  }
}


let temp = new ColoredSquare(2, "red");
let methods = getAllMethodNames(temp);
console.log([...methods]);
skav
  • 1,400
  • 10
  • 16
19

There were a few issues in @MuhammadUmer answer for me (symbols, index i+1, listing of Object methods, etc...) so taking inspiration from it, I came up with this

(warning Typescript compiled to ES6)

const getAllMethods = (obj) => {
    let props = []

    do {
        const l = Object.getOwnPropertyNames(obj)
            .concat(Object.getOwnPropertySymbols(obj).map(s => s.toString()))
            .sort()
            .filter((p, i, arr) =>
                typeof obj[p] === 'function' &&  //only the methods
                p !== 'constructor' &&           //not the constructor
                (i == 0 || p !== arr[i - 1]) &&  //not overriding in this prototype
                props.indexOf(p) === -1          //not overridden in a child
            )
        props = props.concat(l)
    }
    while (
        (obj = Object.getPrototypeOf(obj)) &&   //walk-up the prototype chain
        Object.getPrototypeOf(obj)              //not the the Object prototype methods (hasOwnProperty, etc...)
    )

    return props
}

This function will list all methods of an instance of the class including those inherited, but not the constructor and those of the Object prototype.

Test

The function returns

[ 'asyncMethod',
  'echo',
  'generatorMethod',
  'ping',
  'pong',
  'anotherEcho' ]

listing the methods of an instance of TestClass (typescript)

class Echo  {
    echo(data: string): string {
        return data
    }
    anotherEcho(data: string): string {
        return `Echo ${data}`
    }
}


class TestClass extends Echo {

    ping(data: string): string {
        if (data === 'ping') {
            return 'pong'
        }
        throw new Error('"ping" was expected !')
    }

    pong(data: string): string {
        if (data === 'pong') {
            return 'ping'
        }
        throw new Error('"pong" was expected !')
    }

    //overridden echo
    echo(data: string): string {
        return 'blah'
    }

    async asyncMethod(): Promise<string> {
        return new Promise<string>((resolve: (value?: string) => void, reject: (reason?: any) => void) => {
            resolve('blah')
        })
    }

    * generatorMethod(): IterableIterator<string> {
        yield 'blah'
    }
}
codejockie
  • 9,020
  • 4
  • 40
  • 46
Bruno Grieder
  • 28,128
  • 8
  • 69
  • 101
  • 3
    That's a great snippet and it works. However, a small caveat: it might not work as expected if object has properties defined by `Object.defineProperty` or es5-style `get propertyName() { }`. The problem is here `typeof obj[p] === 'function'`. The thing is property `obj[p]` getter will actually **get called**, but with incorrect `this`. So if property getter uses `this` it will lead to unexpected results, e.g. crashes. Solution - here `typeof obj[p] === 'function'` instead of `obj` use the original one passed to this `getAllMethods` (store it in local variable). – Wicharek Mar 27 '17 at 19:44
  • @Wicharek can you give an example – Muhammad Umer Mar 18 '18 at 17:53
  • @MuhammadUmer [here is the code](https://gist.github.com/wicharek/01abfd68b99e8206229b90dd0bc917cf) I have successfully used in one of my projects – Wicharek Mar 21 '18 at 14:22
  • 1
    These were close but still have issues with getters, namely everytime you are checking the getter you are actually calling it, which is a recipe for potential disaster, and was blowing up when I was trying to implement. Here is a version fixing that https://gist.github.com/jasonayre/5d9ebd64299bf69c8637a9e03e33a3fb – thrice801 May 19 '19 at 01:09
  • 1
    Nice function works great, it there the possibility to list ONLY public methods? – Magico Jun 17 '20 at 16:20
  • @Magico As far as I know, `you can't`. JavaScript does not have a built-in way to mark methods as public or private. The only way I know to mark down a class method (in a js file) as public or private is by jsDocs. The problem is that the JavaScript's interpreter does not keep any information about JSDoc comments. They are ignored by the interpreter and are only used for documentation purposes. – Tal Kohavy Jan 16 '23 at 21:57
2

To make members of class enumerable you can use Symbol.iterator

I had to get all allowed methods of object (including inherited). So i created class "Enumerable" and all my base classes inherited from him.

class Enumerable {
  constructor() {

    // Add this for enumerate ES6 class-methods
    var obj = this;

    var getProps = function* (object) {
      if (object !== Object.prototype) {
        for (let name of Object.getOwnPropertyNames(object)) {
          let method = object[name];
          // Supposedly you'd like to skip constructor and private methods (start with _ )
          if (method instanceof Function && name !== 'constructor' && name[0] !== '_')
            yield name;
        }
        yield* getProps(Object.getPrototypeOf(object));
      }
    }

    this[Symbol.iterator] = function*() {
      yield* getProps(obj);
    }
    // --------------
  }
}
Santinell
  • 31
  • 3