seasick
seasick

Reputation: 1225

ES6 Iterate over class methods

Given this class; how would i iterate over the methods that it includes?

class Animal {
    constructor(type){
        this.animalType = type;
    }
    getAnimalType(){
        console.log('this.animalType: ', this.animalType );
    }
}

let cat = window.cat = new Animal('cat')

What I have tried is the following with no success:

for (var each in Object.getPrototypeOf(cat) ){
    console.log(each);
}

Upvotes: 60

Views: 25426

Answers (12)

Craig Harshbarger
Craig Harshbarger

Reputation: 2243

I'll add my version here for anyone else who might need it. This version gets all properties and members that are of the type function while excluding anything that exists on Object.prototype and returns an object with the same keys and the functions that go with them.

Example

class Alpha {
  a = 'a'
  b = () => 'b'
  c = function() { return 'c' }
  d() {
    return 'd'
  }
}

class Beta {
  e = 'e'
  f = () => 'f'
  g = function() { return 'g' }
  h() {
    return 'h'
  }
}

const functions = getAllFunction(new Beta())

// functions
// {
//   b: () => 'b',
//   c: function() { return 'c'},
//   d: function() { return 'd' },
//   f: () => 'f',
//   g: function() { return 'g' },
//   h: function() { return 'h' }
// }
function getAllFunctions(source) {
  const functions = {}
  const objectPrototypeKeys = Reflect.ownKeys(Object.prototype)
  let prototype = Reflect.getPrototypeOf(source)

  // properties
  Reflect.ownKeys(source).forEach(key => {
    if (typeof key === 'string' && typeof source[key] === 'function') {
      functions[key] = source[key]
    }
  })

  // methods
  while (prototype && prototype !== Object.prototype) {
    Reflect.ownKeys(prototype).forEach(key => {
      if (typeof key === 'string' && typeof prototype[key] === 'function' && !objectPrototypeKeys.includes(key)) {
        functions[key] = (prototype as any)[key]
      }
    })

    prototype = Reflect.getPrototypeOf(prototype)
  }

  return functions
}

Upvotes: 1

haouarin
haouarin

Reputation: 1601

This is an enhanced version of the answer suggested here https://stackoverflow.com/a/35033472/3811640

I add the parameter deep, deep= Infinity by default to extract all the functions including parent functions. deep =1 to extract the direct methods of a given class.

getAllMethods = function (obj, deep = Infinity) {
    let props = []

    while (
      (obj = Object.getPrototypeOf(obj)) && // walk-up the prototype chain
      Object.getPrototypeOf(obj) && // not the the Object prototype methods (hasOwnProperty, etc...)
      deep !== 0
    ) {
      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)
      deep--
    }

    return props
  }
  

class Foo {
  
  method01 (){
  }
  
  method02 (){
  }
  
  }
  
  
  class FooChield extends Foo {
  
  method01(){
  }
  
  method03(){
  }
  } 
  

  
  console.log('All methods', getAllMethods(new FooChield())) 
  
  console.log('Direct methods', getAllMethods(new FooChield(),1))

Upvotes: 2

Aronanda
Aronanda

Reputation: 301

A way to do it getting inherited methods without the Object methods and optionally the getters:

const getMethods = (obj, getters) => {
  const orig = obj
  let keys = []
  do {
    keys = keys.concat(Object.getOwnPropertyNames(obj))
    if (!getters)
      keys = keys.filter(k => !(Object.getOwnPropertyDescriptor(obj, k) || {}).get)
    obj = Object.getPrototypeOf(obj)
  } while (obj && obj.constructor && obj.constructor.name !== 'Object')
  return Array.from(new Set(keys))
    .filter(k => k !== 'constructor' && typeof orig[k] === 'function')
}

Upvotes: 0

Chester Fung
Chester Fung

Reputation: 370

Why the answers here are so complicated? Let's make it simple.

Object.getOwnPropertyNames(...) is the standard and only way to get the name of non-inherit class methods. [return as an array in alphabetical order]

Object.getPrototypeOf(...) is the standard and only way to get the inherit prototype. [return as an Class.prototype]

So, just looping

  Object.getPrototypeOf(Object.getPrototypeOf( 
    Object.getPrototypeOf( Object.getPrototypeOf(  
      ...
        Object.getPrototypeOf( your_object )
      ...
    ))
  ))

until its constructor === Object

  // JavaScript

  class A {
     myWife() {   console.log(4)   }
  }
  class B extends A {
     myParent() {   console.log(5)   }
  }
  class C extends B {
     myBrother() {   console.log(6)   }
  }
  class D extends C {
     mySelf() {   console.log(7)   }
  }
  
  let obj = new D;
  
  function classMethodsA(obj) {
    let res = {};
    let p = Object.getPrototypeOf(obj)
    while (p.constructor !== Object) {
      for(const k of Object.getOwnPropertyNames(p)){
        if (!(k in res)) res[k] = obj[k];
      }
      p = Object.getPrototypeOf(p)
    }
    return res;
  }
  
    
  function classMethodsB(obj) {
    let res = [];
    let p = Object.getPrototypeOf(obj);
    while (p.constructor !== Object) {
      for(const k of Object.getOwnPropertyNames(p)){
        if (!res.includes(k)) res.push(k);
      }
      p = Object.getPrototypeOf(p);
    }
    return res;
  }
  
  
  document.body.innerHTML=`
<p>  -- No Constructor -- ${Object.keys(classMethodsA(obj))}</p>
<p>  -- With Constructor -- ${classMethodsB(obj)}</p>
  `;

Upvotes: 1

lepe
lepe

Reputation: 25200

Another way to get them without listing the constructor and other properties:

var getMethods = function(obj) {
    const o = Reflect.getPrototypeOf(obj);
    const x = Reflect.getPrototypeOf(o);
    return Reflect.ownKeys(o).filter(it => Reflect.ownKeys(x).indexOf(it) < 0);
}

Upvotes: 1

Basav
Basav

Reputation: 3406

Try this one linear way, if you need just the functions (like replacement to _.functions)

function getInstanceMethodNames (obj) {
    return Object
        .getOwnPropertyNames (Object.getPrototypeOf (obj))
        .filter(name => (name !== 'constructor' && typeof obj[name] === 'function'));
}

Upvotes: 7

Leo Dutra
Leo Dutra

Reputation: 182

This is a bit more elaborated but gets methods from the whole prototype chain.

function getAllMethodNames (obj, depth = Infinity) {
    const methods = new Set()
    while (depth-- && obj) {
        for (const key of Reflect.ownKeys(obj)) {
            methods.add(key)
        }
        obj = Reflect.getPrototypeOf(obj)
    }
    return [...methods]
}

Upvotes: 7

goofballLogic
goofballLogic

Reputation: 40489

i know, i know, but hey...

const isGetter = ( x, name ) => ( Object.getOwnPropertyDescriptor( x, name ) || {} ).get
const isFunction = ( x, name ) => typeof x[ name ] === "function";
const deepFunctions = x => 
  x && x !== Object.prototype && 
  Object.getOwnPropertyNames( x )
    .filter( name => isGetter( x, name ) || isFunction( x, name ) )
    .concat( deepFunctions( Object.getPrototypeOf( x ) ) || [] );
const distinctDeepFunctions = x => Array.from( new Set( deepFunctions( x ) ) );
const userFunctions = x => distinctDeepFunctions( x ).filter( name => name !== "constructor" && !~name.indexOf( "__" ) );


// example usage

class YourObject {   
   hello() { return "uk"; }
   goodbye() { return "eu"; }
}

class MyObject extends YourObject {
   hello() { return "ie"; }
   get when() { return "soon"; } 
}

const obj = new MyObject();
console.log( userFunctions( obj ) ); // [ "hello", "when", "goodbye" ]

Upvotes: 25

Pa Ye
Pa Ye

Reputation: 1838

Since methods on ES6 class are non-enumerable, you have no other option but to use Object.getOwnPropertyNames() to get an array of all its properties.

After achieving that, there are several ways to go about extracting methods, the simplest of which might be using Array.prototype.forEach().

Check out the following snippet:

Object.getOwnPropertyNames(Animal.prototype).forEach((value) => {
    console.log(value);
})

Upvotes: 6

Jules
Jules

Reputation: 15199

If you need to get superclass methods as well, you can call Object.getPrototypeOf() repeatedly until you find them all. You'll probably want to stop when you get to Object.prototype, because the methods in there are fundamental and you usually don't want to touch them with any code that uses reflection.

The question Get functions (methods) of a class has an answer that includes a function for doing this, but it had a few shortcomings (including using a loop condition which had a side effect of modifying a function argument, which I figure makes two questionable code style choices in a single line of code...), so I've rewritten it here:

export function listMethodNames (object, downToClass = Object)
{
    // based on code by Muhammad Umer, https://stackoverflow.com/a/31055217/441899
    let props = [];

    for (let obj = object; obj !== null && obj !== downToClass.prototype; obj = Object.getPrototypeOf(obj))
    {
        props = props.concat(Object.getOwnPropertyNames(obj));
    }

    return props.sort().filter((e, i, arr) => e != arr[i+1] && typeof object[e] == 'function');
}

As well as fixing the bug in the original code (which didn't copy the object into another variable for the loop, so by the time it was used for filtering in the return line it was no longer valid) this gives an optional argument to stop the iteration at a configurable class. It'll default to Object (so Object's methods are excluded; if you want to include them you can use a class that doesn't appear in the inheritance chain... perhaps making a marker class e.g. class IncludeObjectMethods{} might make sense). I've also changed the do loop to a clearer for loop and rewritten the old-style function ... filter function into an ES6 arrow function to make the code more compact.

Upvotes: 0

Ponmudi VN
Ponmudi VN

Reputation: 1553

check this fiddle

https://jsfiddle.net/ponmudi/tqmya6ok/1/

class Animal {
    constructor(type){
        this.animalType = type;
    }
    getAnimalType(){
        console.log('this.animalType: ', this.animalType );
    }
}

let cat = new Animal('cat');

//by instance
document.getElementById('1').innerHTML = Object.getOwnPropertyNames(cat);

//by getting prototype from instance
document.getElementById('2').innerHTML = Object.getOwnPropertyNames(Object.getPrototypeOf(cat));

//by prototype
document.getElementById('3').innerHTML = Object.getOwnPropertyNames(Animal.prototype);

Upvotes: 2

Paul
Paul

Reputation: 141827

You can use Object.getOwnPropertyNames on the prototype:

Object.getOwnPropertyNames( Animal.prototype )
// [ 'constructor', 'getAnimalType' ]

Upvotes: 82

Related Questions