OK, lets first simplify the code a little. I've refactored out that foo
is a method, which is not necessary do demonstrate the unexpected behaviour.
function foo(a) {
// var x, y, bar - hoisting
function bar() {
console.log(x);
console.log(y);
console.log(a.x);
}
with (a) {
var x = 20;
var y = 30;
bar();
}
}
foo({x:10});
So what happens when we call foo
?
- An execution context is set up and filled with the declared functions and variables. This is what colloquially is referred to as "hoisting". In
foo
, there are the functionbar
and the variablesx
andy
(and the functionfoo
itself and its argumenta
, only in my refactored version). This is the scope to whichbar
has access to. - The with statement is executed. It exchanges the current lexical environment with one that is based on the
a
object - any properties of that are accessible like variables now. - The value
20
is assigned to anx
. What is thisx
? When resolving that identifier, thea
object is checked and - oh - it has a binding with that name! So we put the value in that binding, which will put the20
on the.x
property of the object. - The value
30
is assigned to any
. What is thisy
? Again, the current lexical environment is checked but we don't find any
property on thea
object. So we go on to the parent environment, which is the one with thex
,y
,bar
variables created above. Indeed, here we find any
variable, so we put the value30
in that slot. - The
bar
function is called. Again, a new execution context is set up (like above), with the one from step 1 as its parent scope (which was determined by the lexical position ofbar
infoo
, when thebar
function was instantiated - lexical closure). Now we log those three expression of interest:x
resolves to the variablex
in the scope offoo
, which still has the valueundefined
.y
resolves to the variabley
in the scope offoo
, which holds the value30
that we just assigned.a.x
resolves to thex
property of thea
object, which holds the value20
that we just assigned.