1

I was experimenting with some bits of code below and everything seems to be working as expected. The goal was to bind a.val to b.val such that when a.val changed, b.val would also change automatically.

// instantiate 
var a = {}
var b = {}

// set value
a.val = 10

// check values
console.log("A.val is:", a.val) // 10
console.log("B.val is:", b.val) // undefined

// bind values
Object.defineProperties(a, {
  val: {
    configurable: true, // can be changed or deleted
    enumerable: true,   // can be iterated over
    get: ()=> this.val,
    set: (value)=> {
        this.val = value
        b.val = value
        return value
    }
  }
})

// reset value
a.val = 20

// check values
console.log("A.val is:", a.val) // 20
console.log("B.val is:", b.val) // 20

So I wanted to move forward with this concept and abstract the Object.defineProperties part into a function. I thought I could make a for loop and loop through the properties of one object, call Object.defineProperty. But I ran into some issues:

function getSet(props){
  for(var key in props){
    Object.defineProperty(props, key, {
      configurable: true, // can be changed or deleted
      enumerable: true,   // can be iterated over
      get: ()=> this[key],
      set: (value)=> {
          this[key] = value
          return value
      }
    })
  }

  return props
}

var a = getSet({name: 'the letter a', age: 12})

a.name // undefined
a.name = 'the letter a'
a.age // 'the letter a'

Essentially I have inadvertently bound the two properties of the object I was passing to the getSet function. Furthermore I have erased their initial values.

My question is, why does that happen, and how do I get closer to my goal, of binding the properties of one object to another.

Through testing several other possible solutions I ran into a Maximum call stack size exceeded error: I presume when referencing this in the getter or setter, the act of getting the value from the object I am trying to define properties on triggers a call to the getter, and loops indefinitely. Not entirely sure when/why this happens.

Jamie S
  • 857
  • 1
  • 7
  • 13
  • `this` does not refer to what you think it does. In your `getSet` function `this` refers to the window. – Tibrogargan May 21 '18 at 20:24
  • @Tibrogargan: You're right. although. That doesn't entirely solve my problem. I implemented the same thing with a `var copy = Object.assign({}, props)`, and replaced `this` with `copy`. The properties remained bound. If I change `age` it changes `name` and visa versa. – Jamie S May 21 '18 at 20:51

1 Answers1

1

Your current code doesn't really work. The two major flaws are

  • you cannot use arrow functions with this in an object literal. The this value will be the module object (in node) or global object (window in browsers), not the current object.

    • If it had worked as you expected and referenced the receiver of the current access (which also happened when you experimented with a proper method), you would read the value of a.val from a.val and write the value for a.val to a.val, creating the infinite recursion you experienced. You need to use something else for the actual storage - in your case, the b object is the proper solution.
    • In the browser, assigning to the name property of the global object will coerce everything to a string
  • in your loop, there's only a single key variable that the getter/setter functions close over. So all of them will use the value in the same place on the store.

To fix this, use

function getSet(proxy, store) {
  for (const key in store) {
//     ^^^^^ block scope
    Object.defineProperty(props, key, {
      configurable: true,
      enumerable: true,
      get: () => { return store[key]; },
//                        ^^^^^
      set: (value) => { store[key] = value; }
//                      ^^^^^
    });
  }
  return proxy;
}
Bergi
  • 513,640
  • 108
  • 821
  • 1,164