Notes
-
-
Save DamianMullins/18dde00384db8272ace438b399d9b139 to your computer and use it in GitHub Desktop.
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
Nested bubble are strictly nested
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 scope leads to poorer performance
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
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
Deprecated
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()
!
Playing around with nested scope and shadow identifiers
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
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
Dependency managers use the module approach
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
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
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.
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
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
Can use block scoping with let
to help the engine with garbage collection
let
in the for-loop header binds the i
to the for-loop body
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 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
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()
fromfoo()
- Execute
foo()
, assigning the value returned tobaz
- Invoke
baz()
, which is invoking inner functionbar()
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
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
Revealing module pattern uses closure
Turning a module function into an IIFE turns the module into a singleton
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
Closure is when a function can remember and access its lexical scope even when it's invoked outside its lexical scope
- 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.