Search code examples
javascriptfunctionprivate-memberscallable-objectclass-fields

Create callable objects with private properties without arguments.callee


By callable object I mean an object that's also a function similar to jQuery.

There's plenty of resources for doing this, such as this article.

However problem arise when I try to implement private fields/properties.

Only the method utilizing arguments.callee appears to work.

new class extends Function {
  constructor() {
    super('return arguments.callee._call(...arguments)')
  }
  _call(x) {
    console.log("this object is directly callable")
  }
  #data;
  get data() {
    return this.#data ??= link();
  }
}

But arguments.callee is deprecated and doesn't work in strict mode so it feels dirty doing it this way.

The conventional wisdom is to use a named function, but there appears to be no way to name the function created by the constructor() that I know of.

I could abandon class and private properties altogether and use IIFE to implement private variables but I really like how clean the syntax is with class and private properties and prefer to stick with it if possible.


Solution

  • The approach from my answer to How to extend Function with ES6 classes? works just fine with private class fields:

    function ExtensibleFunction(f) {
      return Object.setPrototypeOf(f, new.target.prototype);
    }
    ExtensibleFunction.prototype = Function.prototype;
    
    class Weird extends ExtensibleFunction {
      #data;
      constructor(data) {
        super((...args) => this._call(...args));
        this.#data = data;
      }
      _call(x) {
        console.log(`This object is directly callable and has access to ${this.#data}`);
      }
      get data() {
        return this.#data;
      }
    }
    
    const example = new Weird('example data');
    console.log(example.data);
    example();

    Or like this:

    const call = Symbol('call');
    function ExtensibleFunction() {
      const f = () => f[call]();
      return Object.setPrototypeOf(f, new.target.prototype);
    }
    ExtensibleFunction.prototype = Function.prototype;
    
    class Weird extends ExtensibleFunction {
      #data;
      constructor(data) {
        super();
        this.#data = data;
      }
      [call](x) {
        console.log(`This object is directly callable and has access to ${this.#data}`);
      }
      get data() {
        return this.#data;
      }
    }
    
    const example = new Weird('example data');
    console.log(example.data);
    example();