skip to Main Content

Explanation of the idea :

I would like to build a class that can be called like a function without initializing it as an instance. For example, I want to create a class named logger and use it in two ways: the first way is logger("some text") and the second way is logger.info("some text"), using dot notation.

Problems :

The issues I am facing are that I do not want to create a method inside the class that represents the first way, because this method will appear during dot notation. For example:

class logger {

  // if i want it to represents the first way: logger("some text")
  // this will appear if i write logger.log, and i don't want that.
  static log(){}

  // this method what i want it to be accessible in object pattern.
  // example: logger.info()
  static info(){}

}

There is another problem as well:

When I try to access the logger class, it shows me other methods and properties. I want only the methods and properties that I have defined to appear in dot notation. If I, for instance, write logger., it will show me apply and call and bind.

One last thing:

I would like to implement the idea using TypeScript.

2

Answers


  1. To implement a class that can be called both as a function and using dot notation without initializing it as an instance, you can use a combination of static methods and a static call method. Here’s an example implementation in TypeScript:

    class Logger {
      private static instance: Logger;
    
      private constructor() {}
    
      static call(message: string) {
        console.log(`Log: ${message}`);
      }
    
      static info(message: string) {
        console.log(`Info: ${message}`);
      }
    
      static get [Symbol.call]() {
        return Logger.call;
      }
    }
    

    Let’s break down what’s happening here:

    1. We define a Logger class with a private constructor, which means it can’t be instantiated as an instance.
    2. We define two static methods: call and info. The call method will be used when the class is called as a function, and the info method will be used when accessed using dot notation.
    3. We define a static property instance that will hold the instance of the class (even though we can’t instantiate it, we need this property to satisfy the TypeScript compiler).
    4. We define a static getter for the Symbol.call property. This property is a special property in JavaScript that allows an object to be called as a function. By defining a getter for this property, we can control how the class is called as a function.

    With this implementation, you can use the Logger class in both ways:

    Logger("some text"); // Output: Log: some text
    Logger.info("some text"); // Output: Info: some text
    

    As for the issues you mentioned:

    • The call method won’t appear when using dot notation because it’s not a part of the class’s prototype.
    • When you access the Logger class using dot notation, you’ll only see the methods and properties that you’ve defined. The apply, call, and bind methods are part of the Function prototype and won’t be visible.

    Note that this implementation uses a static class, which means it can’t be instantiated as an instance. If you need to create instances of the Logger class, you’ll need to modify the implementation accordingly.

    Login or Signup to reply.
  2. Quoting my above comment …

    The use case [the OP is] presenting with every of its conditions/requirements and its usage already cancels out the implementation as a class. It’s not that [one] just [does not] need it. By description and usage a class implementation just would by utterly wrong. What one need [s] is a plain logger function with [an additional single method] nailed to it (functions are objects too).

    The simple reason why using a class is the wrong approach is that class constructors have to be invoked always with new, a simple invocation, like the OP is seeking for with the described use case, will immediately throw a TypeError.

    A running JS implementation which does exactly what the OP is looking for might look similar to and as straightforward as the following provided code …

    // -- begin of logger module scope. --
    
    function loggerInfo(text) {
      console.info({ loggerInfo, text });
    }
    function logger(text) {
      console.log({ logger, text });
    }
    
    // - let `loggerInfo` be the `logger`'s
    //   enumerable, non configurable, non
    //   writable `info` property/method.
    
    Reflect.defineProperty(logger, 'info', {
      enumerable: true,
      value: loggerInfo,
    });
    
    // export default logger;
    
    // -- end of logger module scope. --
    
    
    // another script block or module scope.
    
    logger('directly invoked logger.');
    logger.info('logger's `info` method invoked.');
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    The above code as valid, tanspiling/compiling and running TS code can be found at this TS playground.

    And regarding following of the OP’s non problem

    there’s also another problem:
    When I try to access the logger class, it shows me other methods and properties. I want only the methods and properties that I have defined to appear in dot notation. if i write logger., it will show me apply and call and bind

    This is not a problem, neither of the logger approach/implementation nor of the language itself; it’s just how your IDE works.

    A class does manifest itself in a constructor function, thus if you access a class you actually access a special function, and functions have methods too. Every function inherits call, apply and bind from Function.prototype. Thus these methods are never owned by a function itself, hence they will not show up as own or own enumerable properties. Your IDE is just clever enough to recognize the inheritance/prototype chain and show these methods as available properties.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search