|
# This code demonstrates how CS scoping works within a file. Scroll down to the end to |
|
# see how cross-file scoping works. |
|
|
|
# Here are the rules. |
|
# |
|
# Scoping is all lexical. Read the file from top to bottom to determine variable scopes. |
|
# |
|
# 1) When you encounter any variable in the top-level nesting, its scope is top level. |
|
# 2) Inside a function, if you encounter a variable name that still exists in an outer scope, then that |
|
# variable name refers to the variable in the outer scope. (This is "closure".) |
|
# 3) Inside a function, if you encounter a variable name that does not exist in an inner scope, then a |
|
# new local variable is created, and its scope only gets hoisted to the top of the immediately |
|
# enclosing functions. |
|
# 4) Top level variables go out of scope when you reach the bottom of the file. |
|
# 5) Function-scoped variables go out of scope when you reach the lexical end of the function. (As a |
|
# consequence, two lexically independent functions in a file can reuse common names like "i", "s", |
|
# etc. without side effects.) |
|
# 6) Scoping rules don't change according to casing: bar, Bar, BAR, and $bar all have the same |
|
# rules. |
|
# |
|
# If the rules above are hard to grok, then it's probably best to either experiment yourself or to |
|
# simply learn by example. |
|
|
|
BANNER = '\n--------------' |
|
|
|
|
|
# BASIC SCOPING: independent functions can declare local variables. |
|
# |
|
# The first example should not be surprising to most folks. The variables "a" in f1 are f2 |
|
# are not shared. |
|
|
|
f1 = -> |
|
console.log BANNER |
|
console.log a # undefined |
|
a = 1 |
|
console.log a # 1 |
|
# end of a's scope |
|
|
|
f2 = -> |
|
console.log BANNER |
|
console.log a # undefined |
|
a = 2 |
|
console.log a # 2 |
|
# end of a's scope |
|
|
|
# f1 and f2 are still in scope |
|
f1() # calls the f1 we defined above |
|
f2() # calls the f1 we defined above |
|
|
|
# there is no "a" at outer scope |
|
console.log BANNER |
|
console.log "Is a in scope? #{a?}" # false |
|
|
|
# CLOSURES: functions can easily alias variables in outer lexical scope |
|
# |
|
# CS has normal closure behavior |
|
Accumulator = (seed) -> |
|
initial_value = seed * 2 |
|
return -> |
|
initial_value += 1 # refers to initial_value above |
|
return initial_value |
|
|
|
# If you want to execute a few statements inside a new scope, CS lets |
|
# you create one with its "do ->" idiom. It translates to this is JS: |
|
# (function() { // stuff })(); |
|
do -> |
|
# new scope here, so we don't pollute the top level namespace |
|
console.log BANNER |
|
f_incr = Accumulator(5) # Accumulator refers to same Accumulator above |
|
console.log f_incr() # 11 |
|
console.log f_incr() # 12 |
|
# f_incr falls out of scope |
|
|
|
console.log f_incr? # false, out of scope |
|
|
|
# CLASSES: "this" is explicit, unlike Ruby self |
|
# |
|
# CS does not conflate local variables with instance variables. |
|
class Person |
|
constructor: (name) -> |
|
this.name = name |
|
|
|
add_friend: (name) -> |
|
# name and this.name are two different concepts, obviously |
|
console.log "#{name} and #{this.name} are friends" |
|
|
|
do -> |
|
console.log BANNER |
|
person = new Person("alice") |
|
person.add_friend("bob") # bob and alice are friends |
|
|
|
# At this point, we are back at top-level scope, and these variables are |
|
# defined: |
|
|
|
console.log BANNER # same string as above |
|
console.log f1, f2, Accumulator, Person # [Function] [Function] [Function] [Function: Person] |
|
console.log person? # false, person is not in scope |
|
|
|
# SHARED VARIABLES: CS is for consenting adults. |
|
# |
|
# We can define a new variable PLANET that is available at all scopes below it: |
|
PLANET = "Earth" |
|
|
|
do -> |
|
console.log BANNER |
|
f = -> |
|
g = -> |
|
h = -> |
|
mars = "Mars" # local variable |
|
# PLANET is already in scope, so it stays in its existing scope. |
|
PLANET = mars # hey, we're tired of Earth |
|
return PLANET |
|
return h |
|
return g |
|
console.log f()()() # Mars |
|
console.log PLANET # Mars (our call to the innermost function was touching the top-level PLANET) |
|
|
|
# Back out at top-level scope, f, g, h, and mars no longer exist |
|
console.log f?, g?, h?, mars? # all false |
|
|
|
# GOTCHAS?? |
|
# |
|
# Try to be tricky, and create subtle coupling at the top level by slyly putting a |
|
# variable into top-level scope without assigning to it first. |
|
try |
|
console.log BANNER |
|
console.log should_be_local # will be undefined |
|
catch e |
|
console.log "We got a ReferenceError, so no subtle bugs: #{e}" |
|
|
|
# GOOD PRACTICES: Namespace your top-level variables. |
|
Tree = |
|
root: "thing that goes into the ground to get minerals" |
|
bark: "protective covering" |
|
|
|
Dog = |
|
root: -> console.log "dog is rooting around for foot" |
|
bark: -> |
|
bark = "RUFF!!!" # no ambiguity with Dog.bark, this is just a local |
|
console.log bark |
|
|
|
# No ambiguity here. |
|
console.log BANNER |
|
console.log Tree.root |
|
console.log Tree.bark |
|
Dog.root() |
|
Dog.bark() # RUFF!!!! |
|
|
|
# Hopefully, this covers 99% of the scoping situations you're likely to create in your own codebase. |
|
# |
|
# Some final advice: |
|
# 1) Use descriptive names to avoid unintentional naming collisions. |
|
# 2) Use objects like Tree and Dog to create namespaces. |
|
# 3) Use functions and "do" to create smaller scopes. |
|
# 4) Use naming conventions for top-level variables, especially classes and constants. |
|
|
|
# Cross-file scoping. |
|
# |
|
# You don't have to worry about naming collisions between multiple files in CoffeeScript. |
|
# CoffeeScript automatically wraps all files in a function closure, which means all variables |
|
# inside a file, even top level variables, are scoped to the function wrapper. |
|
# |
|
# Of course, there are occasions when you need scoping to cross file boundaries. For very |
|
# small projects, you can use the -b option of the compiler to suppress the function closure |
|
# wrappers, but this is very brittle. The preferred approach is to selectively add variables |
|
# to exports or window. I'm not going to cover that here, because the details of how you |
|
# do that are really more of a Javascript issue than a Coffeescript issue, and the solutions |
|
# largely depend on the size of your project. |
|
|
|
|
|
|