Skip to content

Instantly share code, notes, and snippets.

@chadfurman
Last active January 31, 2017 04:12
Show Gist options
  • Save chadfurman/b03e64e98ecc83b3a64b4ee302665ca3 to your computer and use it in GitHub Desktop.
Save chadfurman/b03e64e98ecc83b3a64b4ee302665ca3 to your computer and use it in GitHub Desktop.
Hour3

Dynamic Scope

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();

zz: Scope

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

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

Exercise 1

  1. Fix the code so it prints out the alphabet A-Z in the console.
  2. 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
  1. 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)

Exercise 1: Solution

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 Keyword

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"

  1. default binding rule the call on line: foo(). This is a normal function call If you are in "strict" mode, default the this keyword to the undefined value. If you are not in strict mode, default to the global object. It's strict mode of the function being called.

  2. implicit binding rule o2 and o3 both have references to the foo function. on line 6, both foo and o2.foo are both references to the same object When the call site looks like line o2.foo(); -- the o2 object becomes the this keyword, same with o3

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"
}

Binding Confusion

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

Explicit Binding

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment