Variable Scope

You need to know three rules to understand scoping in CoffeeScript:

  1. Every function creates its own scope.

  2. Functions are the only constructs that create scope.

  3. Each variable lives in the outermost scope in which a value is (potentially) assigned to it.

The first two rules are an innate part of the JavaScript language. (More precisely, it’s how variables are scoped with the var keyword. A different scoping keyword, let, is also supported in the ECMAScript 6 draft spec and a handful of forward-looking JavaScript runtimes.) CoffeeScript follows suit, which means that conditionals and loops do not create scope.

The last rule allows CoffeeScript to do away with var. If you write myBologna = ’Oscar Mayer’, it’s understood that you want to create a local variable named myBologna. JavaScript, by contrast, would give that variable global scope unless explicitly told to do otherwise. Be warned, however, that this rule can exacerbate the problem of shadowing, where what was intended as a variable declaration instead overwrites the value of an existing variable. Take care not to use the same variable name in two different, nested scopes.

All Functions Are Closures

One of JavaScript’s distinctive features is that all functions are closures, meaning that they have access to all variables in all surrounding scopes—regardless of where they’re called from. CoffeeScript inherits this important semantic. To give a trivial example:

 X = 5
 sumXY = -> X + Y
 Y = 7
 console.log sumXY()
 12

Both X and Y are declared in the scope surrounding sumXY, so the function has access to both variables. It doesn’t matter that Y isn’t mentioned until after the function declaration. Variable scope is determined at compile time. Assigning a value to Y anywhere in that scope is sufficient to make Y live there.

Now let’s look at a trickier example:

 showCount = (->
  count = 0
  ->
  count += 1
  console.log count
 )()
 showCount()
 showCount()
 showCount()
 1
 2
 3

Let’s break this down:

  1. The inner function is defined to increment count and then display that value.

  2. The IIFE declares count, assigns it an initial value of 0, and returns the inner function.

  3. Because the inner function is returned by the IIFE, that’s what gets assigned to showCount.

So in the outermost scope, the count variable is hidden from us. But it’s accessible to showCount because that function is defined within the scope where count lives.

Other languages have features such as “private variables” and “static variables.” JavaScript and CoffeeScript don’t. Instead, functions give you as much control over variable scope as you want.

Capturing Variables

Although closures are a powerful tool, they do lead to counterintuitive behavior. Here’s an example adapted from another book I wrote:

 for​ i ​in​ [1, 2, 3]
  setTimeout (-> console.log i), 0
 3
 3
 3

The loop iterates over the numbers 1, 2, and 3, so why is the output the number 3 three times? Because when the function is defined and when it’s passed to setTimeout are irrelevant. All that matters is the value of i when the function runs. And setTimeout won’t run the function passed to it until after the loop has finished, even if the timeout is 0.

How can we fix this? What we need to do is take the value at the time of the loop iteration and capture it somehow. We could use an array or a hash to store this information, but an easier and more general solution is to define a function on each loop iteration with our value as an argument. For example:

 for​ i ​in​ [1, 2, 3]
  do (i) ->
  setTimeout (-> console.log i), 0
 1
 2
 3

do is a CoffeeScript keyword designed expressly for this purpose: it calls the given function, passing in the variables whose names match those of the arguments. (Remember how I warned you about variable shadowing? This is the one form of shadowing I approve of, since the relationship between the outer i and the inner i is clear.)

I should mention that creating functions on each loop iteration is a bit wasteful. In the three lines above, we’ve managed to allocate six functions—two for each loop iteration. In a loop with several orders of magnitude more repetitions, this would have a real performance impact. Then again, so would setting that many timeouts!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.178.237