Javascript: Notes on Scoping

In the last Javascript discussion I touched on potential scoping problems
with ‘this’. The reality is a little more confusing. The way scoping
works in Javascript may be counter-intuitive to what you expect. Here
are some things to keep in mind, especially with large-scale projects
like CSSchemer where these things can lead to unexpected behavior.

Function Scope

In Javascript a function has a single scope. That is to say, it does
_not_ have block scope. Consider this snippet of Perl code:

  1. sub foo($) {
  2.     my $bar    = shift;
  3.     my $number = 10;
  4.  
  5.     if ($bar) {
  6.         my $number = 20;
  7.         say $number;
  8.     }
  9.  
  10.     say $number;
  11. }

If we call foo() with a true argument then we will see the output

20
10

That’s because Perl has block-scope [1], and within the ‘if’ block we can
redefine the value of $number, which reverts to its previous value
upon exiting the block. The same thing can not be written in
Javascript. For example:

  1. function foo(bar) {
  2.     var number = 10;
  3.  
  4.     if (bar) {
  5.         var number = 20;
  6.         print(number);
  7.     }
  8.  
  9.     print(number);
  10. }

If we call this foo() with a true value we will get

20
20

There is only one scope for the entire function [2], so regardless of
the ‘var’ declaration for the inner number, there is only one number
in scope for the entire function. This behavior may not seem _that_
strange to you, but the main thing I wanted to show is that ‘var’ in
particular does equate to block scoping. Instead it only restricts
something to function-scope. Consider this:

  1. number = 10;
  2.  
  3. function foo(bar) {
  4.     var number = 20;
  5.     print(number);
  6. }
  7.  
  8. foo(true);
  9. print(number);

This will print out

20
10

If the ‘var’ were removed from foo() then the output would be

20
20

since the global number would be changed.

Using Function-Scope to Fake Block-Scope

Ok—I lied when I said the Perl example could not be written in
Javascript. You can do it, but it is not pretty. The solution is to
use anonymous functions to emulate block-scope.

  1. function foo(bar) {
  2.     var number = 10;
  3.  
  4.     if (bar) {
  5.         (function () {
  6.             var number = 20;
  7.             print(number);
  8.         })();
  9.     }
  10.  
  11.     print(number);
  12. }
  13.  
  14. foo(true);

That prints out the same result as the Perl code, because the
anonymous function has its own scope. However, note that even within
the anonymous function we still have to use ‘var’, or we would end up
printing ’20′ twice, because it would clobber the outer definition of
number.

This is—for better or worse—the best way to temporarily redefine a
variable in an arbitrary scope. You will see this technique in many
polished Javascript libraries, and maybe even in CSSchemer.

The Scope of ‘this’

Let’s look at this code:

  1. function Foo() {
  2.     this.foo = "foo";
  3. }
  4.  
  5. var foo = "global foo";
  6.  
  7. Foo.prototype.bar = function() {
  8.     print("bar() this = ", this);
  9.  
  10.     function inner_bar() {
  11.         print("inner_bar() this = ", this);
  12.         print(this.foo);
  13.     }
  14.  
  15.     inner_bar();
  16.  
  17.     print(this.foo);
  18. };
  19.  
  20. ( new Foo() ).bar();

We make a Foo class, that has a foo property. We also have a global
foo variable. Then we define a Foo.bar() method, which has a nested
function printing this.foo. We call that inner function. And then we
print this.foo from within bar() itself.

You may be surprised to know that the output is:

bar() this = [object Object]
inner_bar() this = [object global]
global foo
foo

In particular, the shocking part here is that ‘this’ within
inner_bar() refers to the _global_ scope. Based on lexical scoping
you would expect ‘this’ in inner_bar() to refer to the same ‘this’
within bar(). But Javascript does not behave this way.

I honestly do not have an explanation for _why_ Javascript does this.
In my opinion it makes no sense. The lesson is to just be _very_
cognizant of the wild behavior of ‘this’ in nested functions.

- ———————————————————————-

Notes:

[1] Omitting the ‘my’ declaration for the second $number would result
in the same behavior as the Javascript version. One of the perks—or
problems, depending on your point of view—is that Perl gives you a
lot of control over scope.

[2] Python has the same behavior. One scope for the entire function.
I honestly don’t know how to get around it.

Related posts:

  1. Scoping in Javascript With ‘Let’
  2. Anonymous Functions With Names?
  3. Javascript Array.forEach
  4. Messing With Namespaces and Anonymous Functions
  5. PHP Puts the Un in Unset
0 Comment   |   Posted in Javascript,blog by EricR on January 20, 2010