Lexical environment chain unexpected bindings

Consider the following example:

const PROXY_TRAPS = [
  'get',
  'set',
  'has',

  'defineProperty',
  'deleteProperty',
  
  'getPrototypeOf',
  'setPrototypeOf',

  'getOwnPropertyDescriptor',
  'ownKeys'
];

function Widget() {
  const myTarget = { word: 'incorrect' };
  const handler = Object.create(null);

  for (let blockScoped = 'Hi, I\'m scoped to the \'for\' block along with my neighbours:', i = 0, trap, { length } = PROXY_TRAPS; i < length; ++i) {
    const iterationScoped = 'Hi, I\'m scoped to current iteration!';
    trap = PROXY_TRAPS[i];
    handler[trap] = (t, ...rest) => Reflect[trap](this, ...rest); debugger; //When this function evaluates, it should search for bindings for 'trap' in its lexical environment chain untill it finds it in 'for' block and by the time it executes the value of 'trap' should be the last element of the array!
  }

  debugger; return new Proxy(myTarget, handler);
}

class Clock extends Widget {
  constructor() {
    super();

    this.word = 'tok';
  }

  tick() {
    console.log(this, this.word);
  }
}

const myClock = new Clock();
myClock.tick();

If you check this code in debugger, you would notice that each property of handler object references the correct value where I expect it to dynamically reference the corresponding binding in for block which would me the last entry in the array.

Why is this happening?

It's unclear what you expect the "blockScoped" variables to be scoped to. The block of the for loop is evaluated in each iteration.
Have a look at Explanation of let and block scoping with for loops on StackOverflow.

1 Like

One way I like to understand newer features in JavaScript is to see what they look like when transformed back into older versions using something like Babel/TypeScript.

for (let i = 0; i < 10; i++) {
    const closure = () => {
        i++;
    }
}

This becomes:

// wrap the inner loop body in a function so it has it's own ES3 scope
var _loop_1 = function (i) {
    // each loop gets it's own 'i'
    var closure = function () {
        i++; // we can mutate the inner 'i' at any time
    };
    out_i_1 = i; // but only at the time we exit the 'loop body' write the value back out
};
var out_i_1;
for (var i = 0; i < 10; i++) {
    _loop_1(i);
    i = out_i_1; // get the block 'i' back in sync with the inner 'i'
}
1 Like