1

Note: This isn't a question about best practices. The with statement is obviously something we should avoid in regular JS usage. I'm interested in its behaviour because I'm writing a simple DSL and seeing how I can push JavaScript's limits to make certain features work.


Consider the following code:

var obj = {prop:0};
with(obj) { prop = 1; }
// obj.prop === 1

Whenever we use a variable inside the with block (like prop, above), it first looks to see if that variable is a property of obj. If it is, then it basically converts that variable to obj.prop.

However, if the variable is not found within obj, then the JS engine surfs up the scope chain looking for prop until it reaches the global object as a last resort:

var obj = {};
with(obj) { prop = 1; }
// obj.prop === undefined
// window.prop === 1

Here's my question: In the above example, the JS engine looks for prop within obj. Is there a way to intercept this lookup? I'd like to "trick" the JS engine (in a spec-compliant way, of course) into thinking that obj has every property in existence, so that all variables references withing the with statement are interpreted as obj.variable. Basically, I want this behaviour:

var obj = {};
with(obj) { prop = 1; }
// obj.prop === 1

I thought this would be as simple as proxying obj and intercepting gets, since (I figured) the engine would do a get to see is obj has prop. I thought that I could simply return something other than undefined, and then the with would treat obj as having all properties:

var prox = new Proxy({}, {
    set(target, property, value) { target[property] = value; },
    get(target, property) { return target[property] !== undefined ? target[property] : null; },
});
with(prox) { prop = 1; }

But that doesn't seem to work. Any ideas?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375

1 Answers1

0

While writing this question I discovered the has trap of the Proxy which was the answer to my question. So here's how you achieve the behaviour I was looking for:

var prox = new Proxy({}, {
    has(target, property) { return true; },
});
with(prox) { prop = 1; }
// prox.prop === 1

Edit: Just a note that playing around with these dark arts can cause Chrome's DevTools to crash in certain situations. Just in case that saves anyone time debugging.

  • 1
    For those interested, that's because of [`GetIdentifierReference`](https://www.ecma-international.org/ecma-262/7.0/#sec-getidentifierreference), step 3. It checks whether the environment record has a binding with that name which in case of an object environment record means to check [whether the property exists](https://www.ecma-international.org/ecma-262/7.0/#sec-object-environment-records-hasbinding-n). – Felix Kling May 21 '17 at 06:32
  • MDN has some useful information on the [*Poxy* object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), e.g. not supported in IE. – RobG May 21 '17 at 07:05