Chaining Variable Assignments in JavaScript Words of Caution

Declaring multiple variable expressions on a single line is common practice and also a great shorthand syntax.

However, while reviewing several developer’s work recently, I noticed many are not aware this creates implicit global variables.

1
2
3
4
5
6
(function() {
var foo = bar = baz = 'local';
console.log(foo); // local
console.log(bar); // local
console.log(baz); // local
}());

Can you spot the problem? Try the following:

1
2
3
4
5
6
7
8
9
10
11
12
var bar = 'funky';
var baz = 'disco';
(function helloworld() {
var foo = bar = baz = 'local';
console.log(foo); // local
console.log(bar); // local
console.log(baz); // local
}());
console.log(bar);// local, I thought it would be funky
console.log(baz);// local, I thought it would be disco

Multiple variable chaining on the same line creates a leak to the global namespace. Many developers are surprised that local bar and baz inside helloworld() has overwritten global bar and baz.

Why does this happen?

This is because of how operator associativity works in JavaScript, or more simply how operators with the same precedence are evaluated. The interpreter evaluates the = operator as right-to-left associativity.

Therefore the line:

1
var foo = bar = baz = 'local';

Is easier understood as:

1
var foo = ( bar = ( baz = 'local' ) );

This is evaluated as baz = 'local', baz has not been declared locally so check the Scope Chain we find baz was declared globally.

The result of this expression is local, which is returned to the next operator bar = 'local'. bar is not declared locally, so look to the Scope Chain or assign it to the global namespace and return the value local.

Finally, var foo = 'local' sees there is no operator preceding foo but the keyword var, so creates a local variable named foo and assigns it the value local.

The Solution

To avoid the global leak, we simply need to separate the variable declarations from assignments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var bar = 'funky';
var baz = 'disco';
(function helloworld() {
// method 1
var foo, bar, baz;
foo = bar = baz = 'local';
// or method 2
var foo = 'local';
var bar = 'local';
var baz = 'local';
// or method 3
var foo = 'local',
bar = 'local',
baz = 'local';
console.log(foo); // local
console.log(bar); // local
console.log(baz); // local
}());
console.log(bar); // funky
console.log(baz); // disco

Now this is better, the local variables are contained in their scope and are completely hidden from the global namespace.

Summary

I actually think this pattern looks quite ugly, having to separate declarations from assignments, but since it stops implicit globals then I’m all for it.

Hopefully after this article I won’t be stumbling across quite as many global namespace leaks in the future!


原文链接:Chaining Variable Assignments in JavaScript: Words of Caution