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!

Made with Slides.com