Friday afternoon scoping quiz

# P T Withington (16 years ago)

({ 'foo': 20, 'test': function () { var f = function () { alert("foo:"+ foo); alert("this.foo:"+ this.foo); alert("bar:"+ bar); } with (this) { var foo = 42; var bar = 21; f.call(this); } } }).test();

--

By my reading, foo and bar are declared and initialized in test, and closed over by f. I expect to see:

foo:42 this.foo:20 bar:21

But in Rhino, Firefox, Safari and Opera I am seeing:

foo:undefined this.foo:42 bar:21

Flash 10 gives me the answer I expected. I have not tested other JS engines.

# Brendan Eich (16 years ago)

On Feb 5, 2010, at 2:14 PM, P T Withington wrote:

({ 'foo': 20, 'test': function () { var f = function () { alert("foo:"+ foo); alert("this.foo:"+ this.foo); alert("bar:"+ bar); } with (this) { var foo = 42; var bar = 21; f.call(this); }

"Doctor, it hurts when I use 'with' in JavaScript!"

} }).test();

--

By my reading, foo and bar are declared and initialized in
test, and closed over by f. I expect to see:

foo:42 this.foo:20 bar:21

But in Rhino, Firefox, Safari and Opera I am seeing:

foo:undefined this.foo:42 bar:21

This is correct. The vars are hoisted but the initialization of foo
puts 42 where it found foo on the scope chain, in this.foo. There's no
'bar' property in |this| so that goes in the var.

Flash 10 gives me the answer I expected. I have not tested other JS
engines.

Flash 10 bug.

# P T Withington (16 years ago)

On 2010-02-05, at 17:25, Brendan Eich wrote:

On Feb 5, 2010, at 2:14 PM, P T Withington wrote:

({ 'foo': 20, 'test': function () { var f = function () { alert("foo:"+ foo); alert("this.foo:"+ this.foo); alert("bar:"+ bar); } with (this) { var foo = 42; var bar = 21; f.call(this); }

"Doctor, it hurts when I use 'with' in JavaScript!"

Indeed!

} }).test();

--

By my reading, foo and bar are declared and initialized in test, and closed over by f. I expect to see:

foo:42 this.foo:20 bar:21

But in Rhino, Firefox, Safari and Opera I am seeing:

foo:undefined this.foo:42 bar:21

This is correct. The vars are hoisted but the initialization of foo puts 42 where it found foo on the scope chain, in this.foo. There's no 'bar' property in |this| so that goes in the var.

Interesting! I couldn't convince myself that var hoisting would be quite so literal.

Flash 10 gives me the answer I expected. I have not tested other JS engines.

Flash 10 bug.

I will report. Thanks.

# P T Withington (16 years ago)

On 2010-02-05, at 17:42, P T Withington wrote:

Flash 10 gives me the answer I expected. I have not tested other JS engines.

Flash 10 bug.

I will report. Thanks.

FTR, appears to be already reported as: bugs.adobe.com/jira/browse/ASC

# Dmitry A. Soshnikov (16 years ago)

Hello P,

Saturday, February 6, 2010, 1:14:33 AM, you wrote:

({ 'foo': 20, 'test': function () { var f = function () { alert("foo:"+ foo); alert("this.foo:"+ this.foo); alert("bar:"+ bar); } with (this) { var foo = 42; var bar = 21; f.call(this); } } }).test();

When method .test is called, its this value on entering the context is set to anonymous object. The [[Scope]] of the internal 'f' function expression is set to current scope chain which is at the moment:

Scope chain (test): [ global, {foo: undefined, bar: undefined} ],

where the last object - is an activation object of the execution context of the 'test' function.

After that 'with' statement augment current scope chain with its object which is again main anonymous object:

Scope chain (test): [ global, {foo: undefined, bar: undefined}, {foo: 20, test: function () {...}} ]

And then, when 'f' internal function is activated, its this value is also set to main anonymous object by the .call method. Scope chain of the 'f' function is:

Scope chain (f): [ global, {foo: undefined, bar: undefined}, {foo: 20, test: function () {...}}, {activation object of 'f' context} ]

And then just identifier resolution works which resolved name bindings in the scope chain - from the deep (from the activation object) and up to the top.

That's why 'foo' identifier will be found in the second object of the scope chain (with value 20), and 'bar' - in third.

--

By my reading, foo and bar are declared and initialized in test, and closed over by f. I expect to see:

foo:42 this.foo:20 bar:21

That's possible wihtout using 'with'.

But in Rhino, Firefox, Safari and Opera I am seeing:

foo:undefined this.foo:42 bar:21

Flash 10 gives me the answer I expected. I have not tested other JS engines.

Incorrect behavior.

# Dmitry A. Soshnikov (16 years ago)

Hello P,

Scope chain (f): [ global, {foo: undefined, bar: undefined}, {foo: 20, test: function () {...}}, {activation object of 'f' context} ]

Sorry, scope chain of 'f' context sure will not contain object added by the 'with' statement (my typo), scope chain above was described for 'test' context. And for 'f' is:

Scope chain (f): [ global, {foo: undefined, bar: 21}, {activation object of 'f' context} ]

# P T Withington (16 years ago)

On 2010-02-05, at 18:08, Dmitry A. Soshnikov wrote:

Scope chain (test): [ global, {foo: undefined, bar: undefined}, {foo: 20, test: function () {...}} ]

This is the key that I was missing, as Brendan made clear. Despite foo and bar being declared inside the with, because declarations are hoisted, the var foo is shadowed by the with (this) when the assignment foo = 42 is evaluated.

Thanks for the clarifying illustrations.