开发者

Behavior of local variables in JavaScripts with()-statement

开发者 https://www.devze.com 2022-12-26 14:56 出处:网络
I noticed some weird (and to my knowledge undefined behavior, by the ECMA 3.0 Spec at least), take the following snippet:

I noticed some weird (and to my knowledge undefined behavior, by the ECMA 3.0 Spec at least), take the following snippet:

var foo = { bar: "1", baz: "2" };
alert(bar);
with(foo) {
    alert(bar);
    alert(bar);
}
alert(bar);

It crashes in both Firefox and Chrome, because "bar" doesn't exist in the first alert(); statement, this is as expected. But if you add a declaration of bar inside the with()-statement, so it looks like this:

var foo = { bar: "1", baz: "2" };
alert(bar);
with(foo) {
    alert(bar);
    var bar = "g2";
    alert(bar);
}
alert(bar);

It will produce the following:

undefined, 1, g2, undefined

It seems as if you create a variable inside a with()-statement most browsers (tested on Chrome or Firefox) will make that variable exist outside that scope also, it's just set to undefined. Now from my perspective bar should only exist inside the with()-statement, and if you make the example even weirder:

var foo = { bar: "1", baz: "2" 开发者_高级运维};
var zoo;
alert(bar);
with(foo) {
    alert(bar);
    var bar = "g2";
    zoo = function() {
        return bar;
    }
    alert(bar);
}
alert(bar);
alert(zoo());

It will produce this:

undefined, 1, g2, undefined, g2

So the bar inside the with()-statement does not exist outside of it, yet the runtime somehow "automagically" creates a variable named bar that is undefined in its top level scope (global or function) but this variable does not refer to the same one as inside the with()-statement, and that variable will only exist if a with()-statement has a variable named bar that is defined inside it.

Very weird, and inconsistent. Anyone have an explanation for this behavior? There is nothing in the ECMA Spec about this.


This follows from ECMA-262, 3rd edition, §12.2, as

Variables are created when the execution scope is entered. A Block does not define a new execution scope.

and

A variable with an Initialiser is assigned the value of its AssignmentExpression when the VariableStatement is executed, not when the variable is created.

This means you have to think of a declaration with initializer as two seperate steps: first, you create a new variable in the function-local (global) scope when entering the function (executing the script), but you don't assign a value until reaching the statement. In your examples, at this point the scope chain has changed due to with(), and the assignment sees the overloaded varriable, leaving the newly created one undefined.

This means

with({ foo : 42 }) {
    var foo = 'bar';
}

is parsed as

with({ foo : 42 }) {
    var foo;
    foo = 'bar';
}

which is equivalent to

var foo;
({ foo : 42 }).foo = 'bar';

and therefore leaving foo uninitialized.


I think you're just misunderstanding the scoping a bit. Not that I blame you, if you're coming from most other languages it makes you go "WTF?". Javascript has what I call "FUBAR Scoping" (I'm sure there's a proper term here but I never bothered to learn it...knowing how it works > knowing what it's called, at least to me).

Let's make the example even simpler:

with({}) {
    var foo = "test";​
}
alert(foo); //alerts "test"

with() allows block level definition of a variable, for being passed into a scope (good answer demonstrating this here). But...it doesn't restrict the variable to this scope, it is available afterwards, here's a better example:

with({ bar: 1}) {
    this.foo = "test";
    alert(bar); //alerts: 1"1"
    alert(this); //alerts: "DOMwindow"
}
alert(foo);

If with() was a closure to the element you're dealing with, then this would refer to the { bar: 1} object we passed into the with, but it doesn't :). this in this case is still the global context of window. Since you're still executing in this context, any variables you define there will be available in that context, and therefore available later in the function, after the with(), because they're defined much higher in scope than you would think. Intuition tells you it's defined in a more constrained scope: within the with()...but javascript is different, it all depends what context your closure is running in.

I tried to find a good description for this behavior that gives a lot more scoping examples, this is the best I could find for you. Overall this is more of a closure/scoping question than with() explicitly, this site covers the overall concept in more detail in case I did a terrible job of explaining it...I know it's a backwards concept in many respects, sorry!

0

精彩评论

暂无评论...
验证码 换一张
取 消