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