-
Notifications
You must be signed in to change notification settings - Fork 9
ScopesAndClosures
Scope, in general, refers to how the browser's javascript engine looks up identifier names at run time in order to set how they will be looked up during execution.
The scope of a variable refers to the "zone" where the variable was defined.
Note: That definition implies that there is a lexing phase of the engine which is done prior to executing.
| Type | Context | Keyword |
|---|---|---|
| Global | (default) |
let const var
|
| Function |
function {} |
let const var
|
| Block |
if{ } else{ } for{ } while{ } |
let const
|
Accesible from everywhere in the program.
var a = 1
function print() {
console.log(a)
a = 2
}
print() // 1
console.log(a) // 2Note:
a is declared on global scope. Then it can be accessed from everywhere and any new assignment from everywhere affects the value of a in the rest of the program.
Accesible only from the inner function where it was declared
var a = 1
function print() {
var b = 2
console.log(a, b)
}
print() // 1, 2
console.log(a, b) // "ReferenceError: b is not definedNote:
b is declared in the print body function () {}. Then it can only be accessed from it's inner code.
Accesible only from the inner block where is was declared. Introduced with ES6.
var a = 1
function print() {
if (true) {
let b = 2
}
console.log(a, b)
}
print() // "ReferenceError: b is not defined
console.log(a, b) // "ReferenceError: b is not definedNote: b is declared in the if block {}. Then it can only be accessed from it's inner code, not by the print body function () {}. let is used to declare b as a block scoped varaibles. Declaring b with var would make it to belong to the function's scope.
In JavaScript, all functions have access to the scope "above" them. The "cascade" of scopes is called nested scopes.
var a = 1
function print() {
if (true) {
let b = 2
var printMore = function() {
var c = 3
for (let i = 0; i < 1; i++) {
let d = 4
console.log(a, b, c, d, i)
}
}
printMore()
}
}
print() // 1, 2, 3, 4, 0Note:
- global scope has access to
a -
print: function scope has access toa -
if: block scope has access toa,b -
printMore: function scope as access toa,b,c -
for: block scope as access toa,b,c,d,i
Scope lookup during the lexical phase also stops once it finds the first match. This means you can shadow a variable further up the scope chain.
var a = 1
function print() {
var a = 2 // shadows global 'a' declaration
console.log(a)
}
print() // 2
console.log(a) // 1Note:
In print, a is a function scoped variable. Any assignment will not affect a in global scope.
In Javascript,
varandfunction(){}declarations are hoisted to the top of the current scope; and hence, those identifiers are available to any code in that scope.
var a = 1
function print() {
console.log(a)
var a = 2 // shadows parent 'a' declaration
console.log(a)
}
print() // undefined, 2
console.log(a) // 1Note: Value of a is undefined on first console.log but we could assume that if should have the value of a in global scope.
Raw code
var a = 1
function print() {
console.log(a)
var a = 2 // shadows parent 'a' declaration
console.log(a)
}
print() // undefined, 2
console.log(a) // 1Compiled code
var a = 1
function print() {
var a // a was hoisted
console.log(a) // undefined
a = 2
console.log(a) // 2
}Note: behind the scene, a is hoisted on the top of the function body.
Only
vardeclarations are hoisted
function doSomething() {
console.log(bar) // undefined
console.log(foo) // ReferenceError
var bar = 1
let foo = 2
}
doSomething()function test() {
var foo = 33
if (true) {
let foo = foo + 55 // ReferenceError
}
}
test()let a = 1
function print() {
console.log(a) // ReferenceError: a is not defined
let a = 2 // shadows parent 'a' declaration
console.log(a)
}
print() // ReferenceError: a is not definedMDN // Temporal Dead Zone and errors with let
Everything that is not declared in a local scope, is considered global and can provoke side effects:
Raw code
function increment(num) {
result = num + 1
return result
}
console.log(increment(3)) // 4
console.log(result) // 4Equivalent code
var result
function increment(num) {
result = num + 1
return result
}
console.log(increment(3)) // 4
console.log(result) // 4Note: as not declared with var, const or let, the result variable is considered global and declared in the global scope. That´s a clear unexpected side effect.
What would be the output of this code?
;(function() {
var a = (b = 5)
})()
console.log(b)Note: variable a is declared using the keyword var. What this means is that a is a local variable of the function. On the contrary, b is assigned to the global scope.
var declares a but b is declared as a global variable...
;(function() {
var a = (b = 5)
})()
console.log(b) // 5Equivalent code:
var b
;(function() {
var a
a = b = 5
})()
console.log(b) // 5How to fix
// WRONG
;(function() {
var a = (b = 5)
})()
console.log(b) // 5
// GOOD
;(function() {
var a, b
a = b = 5
})()
console.log(b) // b is not definedClosures are all around you in JavaScript, you just have to acknowledge and embrace them.
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope).
In other words, these functions 'remember' the environment in which they were declared/created.
Here's an example:
function foo() {
var a = 2
return function() {
console.log(a)
}
}
var myFunc = foo() // function () { console.log( a ); }
myFunc() // 2Here's a little more complicated one:
function foo(a) {
return function() {
console.log(++a)
}
}
const myFunc5 = foo(5)
const myFunc8 = foo(8)
myFunc5() // 6 ( 5 + 1 )
myFunc8() // 9 ( 8 + 1 )
myFunc5() // 7 ( 6 + 1 )
myFunc8() // 10 ( 9 + 1 )What would be the output if user clicks on "Button 6"?
function addButtons(num) {
for (var i = 0; i < num; i++) {
var $button = jQuery('<button>Button ' + i + '</button>')
$button.click(function() {
console.log('This is button' + i)
})
$(document.body).append($button)
}
}
addButtons(10)Note: "This is button 10" is the response. Why? The scope of i is addButtons function. Then, each time clicks on a button, the function takes the current value of i, which is 10 at the end of the loop.
function getCallBack(currentIndex) {
return function() {
console.log('This is button ' + currentIndex)
}
}
function addButtons(num) {
for (var i = 0; i < num; i++) {
var $button = $('<button>Button ' + i + '</button>')
$button.click(getCallBack(i))
$(document.body).append($button)
}
}
addButtons(10)or even:
$button.click(
(function(i) {
return function() {
console.log('This is button ' + i)
}
})(i)
)Note: We need to create a new closure with local i for each click callback. Now, the callback is created in a new function scope when the local i exists with its evaluated value at that moment.
Make the countdown work
function countdown(num) {
for (var i = 0; i <= num; i += 1) {
setTimeout(function() {
console.log(num - i)
}, i * 1000)
}
}
countdown(5)
// -1, -1, -1, -1, -1function countdown(num) {
for (var i = 0; i <= num; i += 1) {
;(function(i) {
setTimeout(function() {
console.log(num - i)
}, i * 1000)
})(i)
}
}or even easier:
change the scope from var (function) to let (block)
function countdown(num) {
for (let i = 0; i <= num; i += 1) {
setTimeout(function() {
console.log(num - i)
}, i * 1000)
}
}
countdown(5)