skip to Main Content

I am confused by the way property getters interact with constructor closure.
Below is a minimal example:

function Ctor() {
  let closureX = 0
  
  Object.assign(this, {
    get publicX() { return closureX }
  })
  
  closureX++
  console.log(closureX)
}

let o = new Ctor()
console.log(o.publicX)

Why is o.publicX not changing when closureX does ?

3

Answers


  1. It is simply because that’s what Object.assign does – it does not assign getters or setters, it assigns the values associated with the keys to the other object. MDN Docs already clarifies that getters will be invoked on the source and setters will be invoked on the target:

    The Object.assign() method only copies enumerable and own properties from a source object to a target object. It uses [[Get]] on the source and [[Set]] on the target, so it will invoke getters and setters. Therefore it assigns properties, versus copying or defining new properties. This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters.

    That last sentence is exactly what you are trying to do and it will not work.

    You can verify this be checking its property descriptor and seeing that the new object did not inherit the getter from the source object, only the value that getter returns:

    const object = Object.assign({}, {
        get publicX(){ return 3; }
    });
    
    console.log( object );
    
    const definition = Object.getOwnPropertyDescriptor( object, 'publicX' )
    
    console.log( definition ); // No getter defined, just a value

    If you do want to accomplish this, the solution would probably be the old-fashioned Object.defineProperty to assign the getter:

    function Ctor(){
      
      let closureX = 0;
      
      Object.defineProperty(this, 'publicX', {
          get(){ return closureX; }
      });
      
      closureX++;
    
    }
    
    const ctor = new Ctor();
    
    console.log( ctor.publicX ); // It now returns the internal variable!
    Login or Signup to reply.
  2. You already learnt the why. Now, a way to ‘solve’ this is to create a factory function. Additional advantage may be that with a factory you don’t need the new keyword anymore. For example:

    function XFactory(closureX = 0) {
      return Object.freeze({
        get X() { return closureX; },
        set X(value) { closureX = value; },
        get incrementX() { closureX += 1; return closureX; },
        get decrementX() { closureX -= 1; return closureX; },
      });
    }
    
    const o = XFactory();
    
    console.log(`o.X => ${o.X}`);
    o.X = 15;
    console.log(`o.X = 15; o.X => ${o.X}`);
    console.log(`o.incrementX => ${o.incrementX}`);
    console.log(`o.decrementX => ${o.decrementX}`);
    
    const x = XFactory(41);
    console.log(`The meaning of life is ${x.incrementX} (x.incrementX)`);

    I actually stopped using constructors a couple of years ago and use factories for all kinds of code in a class free object oriented fashion. For example in this flagged enum factory.

    Login or Signup to reply.
  3. let me break the constructor defination function for you.

      Object.assign(this, {
            get publicX() { return closureX }
          })
    

    The Object.assign() is a static method that copies all enumerable own properties from one or more source objects to a target object.

    and get syntax binds an object property to a function that will be called when that property is looked up.

    this is whole brief in short.

    so when you copied the variable , its a deep copy means changes which are implemented further is not higlighted in the copied one.

    i hope this will help.

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