Skip to content

Instantly share code, notes, and snippets.

@DamianMullins
Last active October 11, 2016 13:30
Show Gist options
  • Save DamianMullins/18dde00384db8272ace438b399d9b139 to your computer and use it in GitHub Desktop.
Save DamianMullins/18dde00384db8272ace438b399d9b139 to your computer and use it in GitHub Desktop.
You Don't Know JS: Scope & Closures - Notes

Chapter 2 - Lexical Scope

Lex-time

Two predominant models for how scope works:

  • Lexical scope - used by most programming languages including JS.
  • Dynamic scope

Scopes can be nested

Helpful to think about as scopes as bubbles

Scopes as bubbles

Nested bubble are strictly nested

Look-ups

Scope look-up stops once it finds the first match

Shadowing: the same identifier specified at multiple levels of scope

Scope look-ups always starts at the innermost scope being executed at the time

If you had a reference to foo.bar.baz, lexical scope look-up would apply to find the foo identifier, then object property-access takes over to resolve the bar and baz properties

Cheating Lexical

Cheating lexical scope leads to poorer performance

Eval

Takes a string argument and then treats the contents of that string as though it had been written at that point in the program, allowing you to modify the lexical scope environment

Node running in strict mode operates in its own lexical scope, so declarations made inside of eval() do not modify the enclosing scope

runkit CodePen

Running in browser - not strict mode


setTimeout() and setInterval() can also take a string as their first argument resulting in the same behaviour

The new Function() function constructor takes a string of code as its last argument, slightly safer than eval() but should be avoided

With

Deprecated

Performance

Optimisations occur during the compilation phase by the engine

Some optimisations boil down to beig able to essentially statically analyze the code as it lexes, and predetermine where variable and function declarations are

With the presence of eval() none of these optimisations are performed, no matter where in the code the eval() is

Engine has to assume pessimisically that optimisations will be invalid

Don't use eval() or with()!

Chapter 3 - Function vs. Block Scope

Playing around with nested scope and shadow identifiers

Hiding In Plain Scope

You can "hide" variables and functions by enclosing them in the scope of a function - creating a scope bubble around the code

Principle of Least Privilege - Seems to go against the way our unit testable code is exposed?

Keep private details contained within the scope they are needed, exposing outside of that can lead to them being used in unexpected ways

Global "Namespaces"

Libraries loaded into your program can quite easily collide with each other if they don't properly hide their internal/private functions and variables.

They usually declare a single variable with a suffiently unique name in the global scope, this then acts as a namespace for the libray

Module Management

Dependency managers use the module approach

Functions As Scopes

If "function" is the very first thing in the statement, then it's a function declaration. Otherwise, it's a function expression

Wrapping a function in (function () {...}) creates a function expression

Anonymous vs. Named

Function expressions can be anonymous, but function declarations cannot omit the name

Drawbacks:

  • No useful name displayed in stack traces
  • A function name helps self document the code

Invoking Function Expressions Immediately

IIFE: Immediately Invoked Function Expression

By adding another () onto the end of a function expression you can execute it: (function () {...})()

Naming an IIFE is a good practice to adopt

Can be declared as (function () {...})() or (function () {...}()), These two forms are identical in functionality. It's purely a stylistic choice which you prefer

Can pass arguments to IIFE's

Can use IIFE's to guarantee that undefined is actually undefined

Can invert the order of an IIFE, where the function to execute is given second, after the invocation and parameters to pass to it. This pattern is used in the UMD (Universal Module Definition) project.

Blocks As Scopes

Block-scoping playground

for-loops such as for (var i = 0; i < 10; i++) { cause the i variable to pollute the scope of the enclosing function

with is an example of blck scope

The catch clause of try/catch is an example of block scope

let

The let keyword attaches the variable declaration to the scope of whatever block (commonly a { .. } pair) it's contained in. In other words, let implicitly hijacks any block's scope for its variable declaration

Declarations made with let will not hoist to the entire scope of the block they appear in, as var does

Garbage Collection

Can use block scoping with let to help the engine with garbage collection

let Loops

let in the for-loop header binds the i to the for-loop body

Chapter 4 - Hoisting

Function declarations are hoisted, as we just saw. But function expressions are not

When you see var a = 2;, you probably think of that as one statement. But JavaScript actually thinks of it as two statements: var a; and a = 2;. The first statement, the declaration, is processed during the compilation phase. The second statement, the assignment, is left in place for the execution phase

Functions First

Functions are hoisted first, and then variables

While multiple/duplicate var declarations are effectively ignored, subsequent function declarations do override previous ones

Duplicate definitions in the same scope are a really bad idea and will often lead to confusing results

Chapter 5 - Scope Closure

Nitty Gritty

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();
  • The function bar() has a closure over the scope of foo() (and indeed, even over the rest of the scopes it has access to, such as the global scope in our case)
function foo() {
    var a = 2;

    function bar() {
        console.log( a );
    }

    return bar;
}

var baz = foo();

baz(); // 2 -- Whoa, closure was just observed, man.
  • Return bar() from foo()
  • Execute foo(), assigning the value returned to baz
  • Invoke baz(), which is invoking inner function bar() by different identifier reference
  • bar() is executed, outside of its declared lexical scope

Would expect inner scope of foo() to be garbage collected, closure prevents this. By virtue of where bar() was declared, it has lexical scope closure over the inner scope of foo() which keeps that scope alive for bar(). bar() still has a reference to that scope, and that reference is called closure

Now I Can See

Callback function - using closure

let behavior says that the variable will be declared not just once for the loop, but each iteration. And, it will, helpfully, be initialized at each subsequent iteration with the value from the end of the previous iteration

Loop example showing babel compiled JS

Modules

Revealing module pattern uses closure

Turning a module function into an IIFE turns the module into a singleton

Future Modules

ES6 adds first-class syntax support for the concept of modules

ES6 Module APIs are static (the APIs don't change at run-time)

ES6 modules do not have an "inline" format, they must be defined in separate files (one per module)

The contents inside the module file are treated as if enclosed in a scope closure

Review

Closure is when a function can remember and access its lexical scope even when it's invoked outside its lexical scope

Tools I used to test code whilst reading this book

  • ESNextbin - Create browser programs with ES2015's latest features and use modules from NPM directly in your browser.
  • CodePen - CodePen is a playground for the front end side of the web.
  • RunKit - Tool for prototyping server-side JavaScript.
  • HyperDev - Combining automated deployment, instant hosting and collaborative editing, HyperDev gets you straight to coding.
  • Babel REPL - node repl + babel
  • RawGit - Serves raw files directly from GitHub with proper Content-Type headers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment