Scope & Closures
You-Dont-Know-JS
What is Scope?
What is variables?
Where do variables live?
Compiler theory
var a = 2;
1. Tokenizing/Lexing
2. Parsing (AST)
3. Code-generation
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "Literal",
"value": 2,
"raw": "2"
}
}
],
"kind": "var"
}
Keyword
Identifyer
Punctuator
Punctuator
Numeric
Engine
Compiler - Scope
var a = 2;
1. Compiler tokenize / lex > parse to AST > generate code:
- @ "var a" ask Scope if "a" already exist for current scope
- if exist, C ignore statement and moves on
- else C ask Scope to declare "a" for current scope
2. Engine execute code:
- @ "a = 2" ask Scope for "a" in current scope
- if exist, assign to it
- else if found in nested scope, assign to it
- else throw error
Lexical Scope
scope that is defined @ lex-time
Scope Bubbles
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2, 4, 12
Scope Bubbles
function foo(a) {
var b = a * 2;
function bar(c) {
var b = c;
}
bar(b * 3);
}
foo(2);
function foo(a) {
var b = a * 2;
function bar(c) {
var b = c; // "shadowing"
}
bar(b * 3);
}
foo(2);
Scope Bubbles
function foo(a) {
b = a * 2;
}
foo(2);
console.log(b);
function foo(a) {
var b = a * 2;
}
foo(2);
console.log(b);
function foo(a) {
"use strict";
b = a * 2;
}
foo(2);
function foo(a) {
b = a * 2;
}
foo(2);
console.log(b); // 4
function foo(a) {
var b = a * 2;
}
foo(2);
console.log(b); // ReferenceError: b is not defined
function foo(a) {
"use strict";
b = a * 2;
}
foo(2); // ReferenceError: b is not defined
Cheating Lexical Scope
function foo(str, a) {
eval( str );
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // ?
function foo(str, a) {
eval( str ); // cheating!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
Cheating Scope
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ?
}
foo( "var a = 2" );
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError
}
foo( "var a = 2" );
Cheating Scope
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // ?
foo( o2 );
console.log( o2.a ); // ?
console.log( a ); // ?
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // ?
console.log( a ); // ?
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // ?
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 - leaked global!
function foo(obj) {
"use strict";
with (obj) {
a = 2;
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 - leaked global!
function foo(obj) {
"use strict";
with (obj) { // SyntaxError: Strict mode code
a = 2; // may not include a with statement
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 - leaked global!
JavaScript Scope
is
function-based
Hiding in plain Scope
function doSomething(a) {
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
function doSomethingElse(a) {
return a - 1;
}
var b;
doSomething( 2 ); // 15
Principle of least privelege
function doSomething(a) {
var b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
function doSomethingElse(a) {
return a - 1;
}
doSomething( 2 ); // 15
function doSomething(a) {
function doSomethingElse(a) {
return a - 1;
}
var b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
doSomething( 2 ); // 15
Avoiding collisions
function foo() {
function bar(a) {
i = 3;
console.log( a + i );
}
for (var i = 0; i < 10; i++) {
bar( i * 2 );
}
}
foo();
function foo() {
function bar(a) {
i = 3; // changes i in containing scope
console.log( a + i );
}
for (var i = 0; i < 10; i++) {
bar( i * 2 ); // infinite loop
}
}
foo();
Global "namespace"
var MyNamespace = {
awesome: "stuff",
doSomething: function() {
// ...
},
doAnotherThing: function() {
// ...
}
};
Function as Scope
var a = 2;
function foo() {
var a = 3;
console.log( a ); // 3
}
foo();
console.log( a ); // 2
var a = 2;
(function IIFE() {
var a = 3;
console.log( a ); // 3
})();
console.log( a ); // 2
var a = 2;
(function IIFE( global ) {
var a = 3;
console.log( global.a ); // 2
})( window );
console.log( a ); // 2
var a = 2;
(function IIFE( $ ) {
var a = 3;
console.log( a ); // 3
})( jQuery );
console.log( a ); // 2
var undefined = true;
(function IIFE( undefined ) {
console.log( undefined ); // undefined
})();
console.log( undefined ); // true
var a = 2;
(function IIFE( def ) {
def ( window );
})(function def( global ){
var a = 3;
console.log( a ); // 3
console.log( global.a ); // 2
});
Universal Module Definition
Named vs Anonymous
setTimeout(function() {
console.log("Anonymous");
}, 1);
setTimeout(function timeoutHandler() {
console.log("Named");
}, 1);
function clickHandler(evt) {
evt.preventDefault();
this.removeEventListener("click", clickHandler)
}
el.addEventListener("click", clickHandler);
Examples:
Blocks as Scope
for (var i = 0; i < 3; i++) {
console.log(i);
}
console.log(i); // ?
var foo = true;
var radius = 3;
if (foo) {
var pi = 3.1415;
foo = pi * radius * radius;
}
console.log(pi); // ?
var objects = [{}, {} ,{}];
for (var i = 0; i < objects.length; i++) {
objects[i].click = function() {
console.log(i);
};
}
objects.forEach(function(obj) {
obj.click(); // ?
});
try {
console.debag();
} catch (e) {
var b = e.message;
}
console.log(b); // ?
try {
console.debag();
} catch (e) {
var b = e.message;
}
console.log(b); // ReferenceError
let
function process(data) {
// do something interesting
}
var someReallyBigData = { .. };
process(someReallyBigData);
var btn = document.getElementById("my_button");
btn.addEventListener("click", function click(evt) {
console.log("button clicked");
});
function process(data) {
// do something interesting
}
{
let someReallyBigData = { .. };
process(someReallyBigData);
}
var btn = document.getElementById("my_button");
btn.addEventListener("click", function click(evt) {
console.log("button clicked");
});
var foo = true;
if (foo) {
let bar = foo * 2;
bar = something( bar );
console.log( bar );
}
console.log( bar ); // ReferenceError
for (let i = 0; i < 10; i++) {
console.log( i );
}
console.log( i ); // ReferenceError
const
var foo = true;
if (foo) {
var a = 2;
const b = 3; // block-scoped to the containing `if`
a = 3; // just fine!
b = 4; // error!
}
console.log( a ); // 3
console.log( b ); // ReferenceError!
Hoisting
a = 2;
var a;
console.log( a ); // ?
a = 2;
var a;
console.log( a ); // 2
console.log( a ); // ?
var a = 2;
console.log( a ); // undefined
var a = 2;
a = 2;
var a;
console.log( a );
//---------- Compilation
var a;
//---------- Execution
a = 2;
console.log( a );
console.log( a );
var a = 2;
//---------- Compilation
var a;
//---------- Execution
console.log( a );
a = 2;
foo();
function foo() {
console.log( a );
var a = 2;
}
//---------- Compilation
function foo() {
var a;
}
//---------- Execution
foo();
//------ in scope foo
console.log( a ); // undefined
a = 2;
foo(); // ?
bar(); // ?
var foo = function bar() {
// ...
};
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// ...
};
//---------- Compilation
var foo;
//---------- Execution
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = __self__;
};
Hoisting
foo(); // ?
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
foo(); // 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
//-------- Compilation
function foo() {
console.log( 1 );
}
//-------- Execution
foo(); // 1
foo = function() {
console.log( 2 );
};
foo(); // ?
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}
foo(); // 3
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}
//------- Compilation
function foo() {
console.log( 3 );
}
//------- Execution
foo();
var foo = function() {
console.log( 2 );
};
foo(); // ?
var a = true;
if (a) {
function foo() { console.log( "a" ); }
}
else {
function foo() { console.log( "b" ); }
}
foo(); // "b"
var a = true;
if (a) {
function foo() { console.log( "a" ); }
}
else {
function foo() { console.log( "b" ); }
}
//------ Compilation
function foo() { console.log( "b" ); }
var a;
//------ Execution
foo(); // "b"
a = true;
if (a) {
//...
}
else {
//...
}
Closure
Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope
function foo() {
var a = 2;
function bar() {
console.log( a ); // 2
}
bar();
}
foo();
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz();
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn();
}
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz;
}
function bar() {
fn();
}
foo();
bar();
Closure
function wait(message) {
setTimeout(function timer(){
console.log( message );
}, 1000);
}
wait( "Hello, closure!" );
function setupRobot(name, selector) {
$(selector).click(function activator() {
console.log( "Activating: " + name );
});
}
setupRobot( "Closure Bot 1", "#bot_1" );
setupRobot( "Closure Bot 2", "#bot_2" );
var a = 2;
(function IIFE(){
console.log( a );
})();
Closure <3 loops
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log( i );
}, i * 1000);
}
// ?
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log( i );
}, i * 1000);
}
// 6, 6, 6, 6, 6
for (var i = 1; i <= 5; i++) {
(function() {
setTimeout(function timer() {
console.log( i );
}, i * 1000 );
})();
}
// ?
for (var i = 1; i <= 5; i++) {
(function() {
setTimeout(function timer() {
console.log( i );
}, i * 1000 );
})();
}
// ?
for (var i = 1; i <= 5; i++) {
(function() {
setTimeout(function timer() {
console.log( i );
}, i * 1000 );
})();
}
// 6, 6, 6, 6, 6
for (var i = 1; i <= 5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log( j );
}, j * 1000 );
})();
}
// 1, 2, 3, 4, 5
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log( j );
}, j * 1000 );
})(i);
}
// 1, 2, 3, 4, 5
Block scope revisited
for (var i = 1; i <= 5; i++) {
let j = i;
setTimeout(function timer() {
console.log( j );
}, j * 1000);
}
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log( i );
}, i * 1000);
}
Modules
function foo() {
var something = "We ahead";
var another = [24, 7];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( "/" ) );
}
}
function Module() {
var something = "We ahead";
var another = [24, 7];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( "/" ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = Module();
foo.doSomething(); // "We ahead"
foo.doAnother(); // "24/7"
var foo = (function Module() {
var something = "We ahead";
var another = [24, 7];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( "/" ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})()
foo.doSomething(); // "We ahead"
foo.doAnother(); // "24/7"
Modules
function Factory(id) {
function identify() {
console.log( id );
}
return {
identify: identify
};
}
var foo1 = Factory( "andolf" );
var foo2 = Factory( "lååpez" );
foo1.identify(); // "andolf"
foo2.identify(); // "lååpez"
var foo = (function Factory(id) {
function identify() {
console.log( id );
}
function identify2() {
console.log( id.toUpperCase() );
}
function change() {
// modifying the public API
publicAPI.identify = identify2;
}
var publicAPI = {
change: change,
identify: identify
};
return publicAPI;
})( "andolf" );
foo.identify(); // "andolf"
foo.change();
foo.identify(); // "ANDOLF"
Modern Modules
var Manager = (function Manager() {
var modules = {};
function define(name, dependencies, func) {
var length = dependencies.length,
i = 0;
for (; i < length; i++) {
dependencies[i] = modules[dependencies[i]];
}
modules[name] = func.apply( func, dependencies );
}
function require(name) {
return modules[name];
}
return {
define: define,
require: require
};
})();
Manager.define("foo", [], function() {
return {
sayHi: function() {
return "Hi";
}
};
});
Manager.define("bar", ["foo"], function(foo) {
return {
log: function() {
console.log(foo.sayHi());
}
};
});
var baz = Manager.require("bar");
baz.log(); // "Hi"
ES6 Modules
// foo.js
function sayHi() {
return "Hi";
}
export sayHi;
// bar.js
// import only `sayHi()` from the "foo" module
import sayHi from "foo";
function log() {
console.log( sayHi() );
}
export log;
// main.js
// import the entire "bar" module
module bar from "bar";
bar.log(); // "Hi"
Recap
Scope
Responsible for storing variables in some location and for retrieving those variables at a later time
Lexical Scope
Store variable at lex-time in current scope. Retrieve variable from current scope or enclosing scope(s)
Challenge!
Scope and Closures
By Pontus Lundin
Scope and Closures
Based on the book You don't know JS by Kyle Simpson: https://github.com/getify/You-Dont-Know-JS
- 569