Setter in JavaScript class that can return

86 Views Asked by At

So I have a class XYZ in JavaScript, it has few functions:

class XYZ{
    constructor(){
        this.text=null;
    }
    f1(){
      // Do Something
      return this;
    }

    f2(){
      // Do Something
      return this;
    }

    f3(txt=null){
      if(txt){
          this.text=txt;
          this.show();
      }
      // Do Something
      return this;
    }

    show(){
      console.log(this.text)
    }
}

const obj = new XYZ();
obj.f1().f2().f3("Hello").f1();
obj.f1().f2().f3().f2();

What I want to achieve is that if there is no text to pass to f3, I want to skip the brackets and use it like a getter:

const obj = new XYZ();
obj.f1().f2().f3("Hello").f1();
obj.f1().f2().f3.f2();

I can achieve something similar like this using getter and setter, but I don't want to break the chain calling. Is there a way where we can skip braces if not passing a value, or using the same name getter and method together? Thanks

2

There are 2 best solutions below

0
Alexander Nenashev On BEST ANSWER

You can use Proxy to redirect getters invoked on methods back to the instance with another Proxy. If a method's getter is invoked (like f1.f2) - call the method (f1):

class XYZ {
    constructor() {
        this.text = null;
        const handler = {
            get(_, prop) {
                if (typeof instance[prop] === 'function') {
                    const out = new Proxy(function(...args) {
                        return instance[prop].call(proxy, ...args);
                    }
                    , {
                      get(){
                        // the next chained method is requested, so call the current method
                        out();
                        return handler.get(...arguments);
                      }
                    });
                   return out; 
                   
                }
                return instance[prop];
            }
        };
        const instance = this;
        const proxy = new Proxy(this,handler);
        return proxy;
    }
    f1() {
        this.log('f1');
        return this;
    }

    f2() {
        this.log('f2');
        return this;
    }

    f3(txt=null) {
        if (txt) {
            this.text = txt;
            this.show();
        }
        this.log('f3');
        return this;
    }

    show() {
        console.log(this.text)
    }
    log(name){
      console.log('calling', name + '()', 'on', JSON.stringify(this));
    }
}

const obj = new XYZ();

obj.f1.f2.f3("Hello").f1.show();

const obj2 = new XYZ();
obj2.f1.f2.f3.f2.show();
.as-console-wrapper { top: 0; max-height: 100% !important; }

0
Mr. Polywhirl On

You could adapt a builder pattern to eliminate calls that do not need to take parameters.

Please note that you should always use parenthesis to denote a function call. Accessing a property on an object should not be creating any side-effects. We are not referencing the function, we are invoking it, so you will need the parenthesis; even if we do not with to pass any arguments to it.

A builder lets us modify the variables in any way we want, prior to constructing the final object.

class Point3D {
  constructor(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  toString() {
    return `[Point3D(x=${this.x},y=${this.y},z=${this.z})]`;
  }
}

class _Point3D_Builder {
  constructor() {
    this._x = 0;
    this._y = 0;
    this._z = 0;
  }
  x(_x) {
    this._x = _x;
    return this;
  }
  y(_y) {
    this._y = _y;
    return this;
  }
  z(_z) {
    this._z = _z;
    return this;
  }
  scale(factor) {
    this._x *= factor;
    this._y *= factor;
    this._z *= factor;
    return this;
  }
  scaleBy2() {
    return this.scale(2.0);
  }
  build() {
    return new Point3D(this._x, this._y, this._z);
  }
}

Point3D.Builder = _Point3D_Builder;

const a = new Point3D.Builder().x(1).y(2).build();
const b = new Point3D.Builder().x(3).z(5).scaleBy2().build();

console.log(a.toString()); // [Point3D(x=1,y=2,z=0)]
console.log(b.toString()); // [Point3D(x=6,y=0,z=10)]
.as-console-wrapper { top: 0; max-height: 100% !important; }

Update: I found a nice tutorial that demonstrates this using a Car class:

"Using the Builder Pattern in JavaScript"