Search code examples
javascripttypescriptclassecmascript-6es6-class

JavaScript class with static methods vs object with function properties


In JavaScript (and TypeScript) is there any difference between using a class vs an object to namespace methods from a performance perspective? And are there any other reasons to prefer one option over the other?

export const UserService = {
  async findMany() {
    // ...
  },
  async findById(id: number) {
    // ...
  }
}
export class UserService {
  static async findMany() {
    // ...
  }
  static async findById(id: number) {
    // ...
  }
}



Solution

  • Static methods/fields on a class with no constructor are more-or-less akin to creating a no-op function and attaching properties directly to the instance of that function. They come with downsides.

    Static methods/fields cannot be annotated in an typescript type/interface.

    This means that you are effectively creating singleton-like behaviour by going down the class route here. You'll never be able to have two UserService classes that neatly satisfy the same contract (although they may thru the merits of structural typing, they can't share an interface), and it becomes hard to

    (a) configure the service (the service needs a connection string, for instance... how do you supply that to the static functions?)

    (b) mock the service for testing.

    A more idiomatic approach here would be to define a type/interface that declares your functions

    type UserService = {
        findMany(): Promise<User[]>;
        findById(id:number): Promise<User>;
    }
    
    export const userService: UserService = {
      async findMany() {
        // ...
      },
      async findById(id: number) {
        // ...
      }
    }
    

    then anything that needs a UserService can just accept a parameter that satisfies this contract.

    function greetUsers(userService: UserService){
        //...
    }
    

    This reduces the tight binding implied by using static functions on classes and gives you much more flexibility down the road, for instance, to create a factory function that creates and configures a UserService instance.

    function getUserService(connectionString:string):UserService{
        const us:UserService = {
           //...
        }
        return us;
    }
    

    Static methods on a class will substantially limit your flexibility to do such things.