Theoretical -- not used in JS
function foo() {
console.log(bar); // bar is defined in baz, foo was called from baz, so bar is defined
}
function baz() {
var bar = "bar"; // this is defined in foo because of the callstack
foo();
}
baz();
Wha type of scoping rules does JavaScript have? Exceptions?
- Lexical Scope
eval
&with
allow us to cheat What are the different ways you can create a new scope?- functions
- catch blocks
- curly braces with the
let
keyword in ES6 What's the difference between undeclared and undefined? undefined
is a value -- means doesn't currently have a value- undeclared variables have never been declared -- reference error
Hoisting isn't actually a thing that's in the spec -- it's a mental construct
a;
b;
var a = b;
var b = 2;
b;
a;
First, it compiles. So a,b get declared first. This looks like this:
var a;
var b;
a;
b;
a = b;
b = 2;
b;
a;
This also goes for functions:
// ... after compile time...
var a = b(); // b was decalred, executing b, c is still undefined, so undefined goes into a
var c = d(); // d is declared, but its value is undefined because the expression hasn't executed
a;
c;
function b() {
return c;
}
var d = function() {
return b();
}
This also looks like this after compile:
function b() {
return c;
}
var a;
var c;
var d;
a = b();
c = d();
a;
c;
d = function() {
return b();
};
Functions are hoisted before variables
foo(); // "foo"
var foo = 2;
funciton foo() {
console.log("bar");
}
function foo() { // this function overrides the above declarations
console.log("foo");
}
Wouldn't it be easier if JS just worked top-down? Well, maybe, but what about mutual recursion (when 2 or more functions call eachother)? Without hoisting, mutual recursion would be impossible.
a(1); // returns 39
function a(foo) {
if (foo > 20) return foo; // is 1 > 20?, is 6 > 20? is 16 > 20? is 36 > 20? return 36
return b(foo + 2); // b gets 3, b gets 8, b gets 18, returns 39
}
function b(foo) {
return c(foo) + 1; +1, +1, +1, return 367, 38, 39
}
function c(foo) {
return a(foo*2); // 3 * 2, 8*2, 18 * 2, return 36
}
C header files are manual hoisting, JS just does it automatically
- Fix the code so it prints out the alphabet A-Z in the console.
- Cannot:
- have any global variables at all
- delete or combine any function declarations
- create any new functions (except IIFEs -- hint!)
- Rearrange the order of declarations
- Can/must:
- declare extra variables (as long as they're not global)
- modify (in-place) function declarations/initialization
- add/remove statements/expressions (IIFEs, return params, etc)
- make the fewest changes possbile
( go to here to get code: https://frontendmasters.com/assets/resources/kylesimpson/advanced-javascript.zip)
Wrap everything in an IIFE
Change expression "A" into a declaration, same with B
Change "C" into a declaration so it overrides
Move A()
to the end of the program
Pass F
into E(F)
Change H
to be a declaration to take advantage of hoisting
After K
it stops, we need to call the next function. Replace the TODO
with window[rest[i+1]]()
-- but we can't do global variables, so replace window with a different object that we pass into the containing IIFE
this
starts to look a bit more like dynamic scoping
Every function, while executing, has a reference to its current execution context called this
The execution context -- how the function is called when it's called
The call site is the place in code where the function gets executed
There are 4 rules the this
keyword is bound by -- find the call site and check each rule
function foo() {
console.log(this.bar); // `this` is referencing an object
}
var bar = "bar1";
var o2 = { bar: "bar2", foo: foo };
var o3 = { bar: "bar3", foo: foo };
foo(); // "bar1"
o2.foo(); // "bar2"
o3.foo(); // "bar3"
-
default binding rule the call on line:
foo()
. This is a normal function call If you are in "strict" mode, default thethis
keyword to theundefined
value. If you are not in strict mode, default to theglobal
object. It'sstrict
mode of the function being called. -
implicit binding rule o2 and o3 both have references to the foo function. on line 6, both
foo
ando2.foo
are both references to the same object When the call site looks like lineo2.foo();
-- theo2
object becomes thethis
keyword, same witho3
var o1 = {
bar: "bar1",
foo: function() {
console.log(this.bar);
}
};
var o2 = { bar: "bar2", foo: o1.foo };
var bar = "bar3";
var foo = o1.foo;
o1.foo(); // "bar1"
o2.foo(); // "bar2"
foo(); // "bar3"
}
We're trying to override this.bar
below to print bar1
function foo() {
var bar = "bar1";
baz();
}
function baz() {
console.log(this.bar); // global bar
}
var bar = "bar2"
foo(); // prints bar2
well... that doesn't work, what about this?
function foo() {
var bar = "bar1";
this.baz = baz; // global.baz = baz? NOOP
this.baz(); // this just calls the function baz()
}
function baz() {
console.log(this.bar); // still global bar
}
var bar = "bar2";
foo(); // prints bar2
foo.call(obj)
<-- obj is this
function foo() {
console.log(this.bar);
}
var bar = "bar1";
var obj = { bar: "bar2" };
foo(); // bar1
foo.call(obj); // bar2 <-- explicit binding of `this`
What is "hard binding"?
function foo() {
console.log(this.bar);
}
var obj = { bar: "bar" };
var obj2 = { bar: "bar2" };
var orig = foo;
foo = function(){ orig.call(obj); };
foo(); // "bar"
foo.call(obj2); // still "bar" because `obj` is forced
We could make a utility to do this:
function bind(fn, o) {
return function() {
fn.call(o);
};
}
function foo() {
console.log(this.bar);
}
var obj =- {bar: "bar"};
var obj2 = {bar: "bar2"};
foo = bind(foo, obj);
foo(); // bar
foo.call(obj2); // bar
We can put this utility on the function prototype directly
if (!Function.prototype.bind2) {
Function.prototype.bind2 =
function(o) {
var fn = this; // the function!
return function() {
return fn.apply(o,arguments);
};
};
}
function foo(baz) {
console.log(this.bar + " " + baz);
}
var obj = {bar: "bar"}
foo = foo.bind2(obj); // implicit binding, `this` == `foo`
foo("baz"); // bar baz
bind
is built-in to ES5!
There's a polyfill on MDN