2

I'm trying to dynamically create getters and setters on a class using defineProperty, but the definition of their function depends on variables above the scope of the defineProperty function call. So when the value of some variable and run defineProperty, the getter, when called later, should behave differently than when this variable has a different value at the time defineProperty was invoked.

So something like:

var/let/const/whatever i=5;
defineProperty(SomeClass, 'prop00', ... get() {
  some code that depends on i, but the value of i should be snapshot
} ...)

I've been trying some things, but I'm getting some behavior I do not understand.

In below code, I have a 2x nested loop from 0..1, and a counter (cA), defined in the outer loop, which I reference inside the getter function within the inner loop:

class TestA {}

for (let i = 0; i < 2; i++) {
  let cA = 0;
  for (let j = 0; j < 2; j++) {
    Object.defineProperty(TestA.prototype, 'prop_' + i + "_" + j, {
      get() { return "val_" + i + "_" + j + ":" + cA; }
    });
    cA++;
  }
}

tA = new TestA();

for (let i = 0; i < 2; i++) {
  let cA = 0;
  for (let j = 0; j < 2; j++) {
    console.log(
      "not-really-but-lets-say-expected", "val_" + i + "_" + j + ":" + cA,
      "got", tA["prop_" + i + "_" + j]
    );
    cA++;
  }
}

This is the output:

"not-really-but-lets-say-expected", "val_0_0:0", "got", "val_0_0:2"
"not-really-but-lets-say-expected", "val_0_1:1", "got", "val_0_1:2"
"not-really-but-lets-say-expected", "val_1_0:0", "got", "val_1_0:2"
"not-really-but-lets-say-expected", "val_1_1:1", "got", "val_1_1:2"

Notice the :2 at the end of each line vs. the :0/:1 on the expected values. So you see that values use the last registered value for the counter cA at the end of each iteration of the outer loop. It appears that for each iteration of the outer loop, a separate scope is defined, and kept, and the references inside the getter are to the counter value in that scope, which is shared among the iterations of the inner loop.

I then tried to define a variable inside the inner loop, and copied the value from the counter inside the parent outer loop:

class TestB {}

for (let i = 0; i < 2; i++) {
  let cA = 0;
  for (let j = 0; j < 2; j++) {
    let cB = cA;
    Object.defineProperty(TestB.prototype, 'prop_' + i + "_" + j, {
      get() { return "val_" + i + "_" + j + ":" + cB; }
    });
    cA++;
  }
}

tB = new TestB();

for (let i = 0; i < 2; i++) {
  let cB = 0;
  for (let j = 0; j < 2; j++) {
    console.log(
      "not-really-but-lets-say-expected", "val_" + i + "_" + j + ":" + cB,
      "got", tB["prop_" + i + "_" + j]
    );
    cB++;
  }
}

This results in:

"not-really-but-lets-say-expected", "val_0_0:0", "got", "val_0_0:0"
"not-really-but-lets-say-expected", "val_0_1:1", "got", "val_0_1:1"
"not-really-but-lets-say-expected", "val_1_0:0", "got", "val_1_0:0"
"not-really-but-lets-say-expected", "val_1_1:1", "got", "val_1_1:1"

So unless I'm misinterpreting what's happening here, it seems that these scopes are kept, and assigned somehow to the scope of the getter that was created by the defineProperty function at that point.

Can someone shed some light on this? I've Googled till my eyes bled, but I'm fairly new to JS otherwise. Here's the Fiddle.

Paljas
  • 353
  • 3
  • 10
  • 2
    Functions close over variables in their enclosing scopes, just like in every other language with lexical closure. So when your getter runs, it looks at the value of `i`. Since it doesn't run until after the loop is over, it will always be the last value of `i` from the loop. One of the many many reasons not to define functions inside for loops. – Jared Smith Sep 02 '22 at 14:54
  • 1
    @JaredSmith thanks so much. The link you provided answers all my questions. I was actually more surprised that `i` and `j` took the values during iteration, than that the cA took the latest (hence the not-really-but-lets-say-expected). I agree defining functions inside loops is dangerous. Best is to set and read properties of the object or prototype instead and have a more generic function. My rationale was to limit the computation and 'precompile' the function. Thanks a lot, your comment is the answer. – Paljas Sep 02 '22 at 15:41

1 Answers1

0

The answer was provided by Jared in comment: What is the scope of variables when using defineProperty on Javascript class prototypes?. Thanks so much for you quick answer!

Paljas
  • 353
  • 3
  • 10