Welcome to Part 5 of this review of the Pluralsight and Front End Masters course Advanced JavaScript by Kyle Simpson.
Kyle Simpson is Head of Curriculum for MakerSquare and an evangelist of the open web. He lives in Austin, Texas, and is passionate about all things JavaScript.
He’s written 8 books published by O’Reilly, including six books in the You Don’t Know JS series.
Kyle teaches JavaScript and has eight courses recorded by Front End Masters.
He’s a public speaker, and contributes to the world of OSS.
In this series:
Part 1 – Introduction
Part 2 – Scope
Part 3 – Lexical Scope
Part 4 – Block scoping
Part 5 – Dynamic Scope, Hoisting and this
Part 6 – Closure
Dynamic Scope
We start with a theoretical example, which doesn’t actually exist in JavaScript:
//theoretical dynamic scoping
function foo() {
console.log(bar); //dynamic!
}function baz() {
var bar = “bar”;
foo();
}baz();
In lexical scope we can say bar does not exist when we try to log it.
But if dynamic scoping was the model that JavaScript used, it would look at the call stack, not where the code was written.
The decision for how scoping works in dynamic scoping is a runtime (not author time) decision.
Quiz: Scope
You should now be able to answer these questions:
- What type of scoping rule(s) does JavaScript have? Exceptions?
- What are the different ways you can create a new scope?
- What’s the difference between undeclared and undefined?
(Answers given in this lesson from the course)
Hoisting
(Also see Chapter 4 of the Scope & Closures book)
The word hoisting is not found anywhere in the specification. But it is a conceptual model that many people find useful for understanding how JavaScript behaves.
That are the values on each of these lines of code?:
a; //???
b; //???
var a = b;
var b = 2;
b; //2
a; //???
The concept of hoisting lets us think of the code like this:
var a; //compile phase
var b; //compile phase
a; //beginning of execution phase
b; //undefined
var a = b; //undefined
var b = 2;
b; //2
a; //undefined
Kyle also shows a code example with a function declaration b and a function expression d. At 4 mins 14 secs in we see the hoisted way of thinking about that code example.
Function declarations are hosted before variables, and Kyle says it’s technically possible to prove that. You can’t call a function expression before it’s been given it’s value.
There’s another code example with foo used in four different contexts. Please: never write crazy code like this!
Why Hoisting?
This is all to do with recursion (when a function calls itself). Or more specifically mutual recursion, which is two or more functions calling each other.
Some problems aren’t practically solvable without mutual recursion. Mutual recursion is only possible when we have hoisting.
We see an example of using mutual recursion in JavaScript, and you’re invited to guess the answer.
Kyle says header files in the C language are a form of manual hoisting.
Temporal dead zone
We see the following let gotcha:
function foo(bar) {
if (bar) {
console.log(baz); //ReferenceError
let baz = bar;
}
}foo(“bar”);
Or in more informal terms “lets don’t hoist”
Exercise 1
You are invited to fix a code sample so that it prints out the alphabet A-Z in the console. There are a number of restrictions to make the challenge more interesting.
We see the solution in the next clip.
this Keyword
(In addition to, ahem, this, you should also have a read of Chapter 1 and Chapter 2 of Kyle Simpson’s excellent book You Don’t Know JS: this & Object Prototypes.)
The idea is to give us a strong contrast between the lexical scoping model (author time decision) and the this keyword.
What you need to know definition:
Every function, while executing, has a reference to its current execution context called this.
JavaScript’s version of “dynamic scope” is this.
We see the two metaphorical building again: one for lexical scope and another for dynamic scope.
This this keyword has probably the most misunderstood and confused mechanism in JavaScript, but it need not be magic any longer because there are just a few rules to learn along with their order of precedence.
There are four rules for how the this keyword gets bound.
They all depend on the call site, which is where a function gets executed with its open close parentheses.
Kyle says it doesn’t matter where a function is declared, whether there’s func, classes or objects or anything else involved.
Default Binding
We will learn these in reverse order of precedence. So we first look at the 4th rule in order of precedence – the default binding rule:
function foo() {
console.log(this.bar);
}var bar = “bar1”;
var o2 = { bar: “bar2”, foo: foo };
var o3 = { bar: “bar3″, foo: foo };foo(); //”bar1″
o2.foo(); //”bar2″
o3.foo(); //”bar3”
We know that this needs to reference on object, not a primitive such as a number or a boolean.
The first time we call foo is on line 9:
foo(); // “bar1”
This is a plain reference to the function, and the default binding rule applies here (because none of the other three rules apply).
If you are in strict mode, default the this keyword to the undefined value.
If you are not in strict mode, default the this keyword to the global object.
This applies at the time we are invoking this, so if “use strict” was only placed inside the foo function above the console.log, this would be defaulted to undefined.
Implicit Binding
The 3rd of the four rules is the implicit binding rule and the earlier code sample works for this rule as well.
In JavaScript everything is a reference to an object. Everything is a reference to a function.
On lines 6 and 7 we are implicitly putting a reference to a function on our objects:
var o2 = { bar: “bar2”, foo: foo };
var o3 = { bar: “bar3”, foo: foo };
On line 10 we make a reference to a function via the object property reference.
o2.foo(); // “bar2”
When the call site looks like the above, the 3rd of the four rules, the implicit binding rule comes into effect:
“That object (at the call site) becomes the this binding”
We get the exact same mechanism with 03 on the next line.
To reinforce the fact that it doesn’t matter where a function is declared, we see another code sample that produces the same console outputs via the same rules.
There’s a great question from one of the viewers:
“What happens if o2 doesn’t have a var property? Does it default back to the global bar?”
Kyle answers that it does not default back to the global bar, but to fully answer that question we need to understand the prototype chain, which we cover later on in this course.
Kyle says that in his “this & Object prototypes” book he’s tried to distill the specification rules down into a human friendly “what you need to know” form.
Binding confusion
Kyle takes a detour to clear up some binding confusions.
function foo() {
var bar = “bar1”;
baz();
}
function baz() {
console.log(this.bar);
}var bar = “bar2”;
foo();
This is a distillation of an old question he found on StackOverflow. Developers try to find a way to join the lexical scoping mechanism with the this scoping mechanism.
The baz function here represents some 3rd party library. The foo function represents the developers code.
The developer wants to make it reference his own local lexical environment.
This is impossible to do. They are two fundamentally different mechanisms that don’t crossover in any way.
We see an incorrect attempt at solving this problem. The details of this aren’t very important because the main takeaway is above.
Explicit Binding
function foo() {
console.log(this.bar);
}var bar = “bar1”;
var obj = { bar: “bar2” };foo(); // “bar1”
foo.call(obj); // “bar2”
The explicit binding rule says:
If you use .call or .apply at the call site, and an argument is passed in, the first argument is the this binding
Kyle goes on another little detour, talking about this bindings getting apparently lost. It’s great that the this keyword is flexible, but sometimes it’s too flexible and frustrates us.
Fortunately there’s a variation pattern around explicit binding that works and it is called 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(); // “bar1”
foo.call(obj); // “bar2”
A few minutes later we see that we can use this pattern with the function prototype. This avoids making our utility global, and makes it available to all functions.
Because this is such a useful mechanism, as of ES5 .bind has been added directly to the function prototype. Included in the MDN documentation is a polyfill that looks quite similar but more advanced than what Kyle just showed.
The new keyword
Kyle says there may be some preconceptions that need to be set aside because in many languages the new keyword is used for instantiating a class.
1. JavaScript does not have classes (at least not like in other languages, Kyle will talk about ES6 classes later)
2. The new keyword doesn’t have anything to do with instantiating classes
The new keyword turns a function call into what the spec refers to as a constructor call (although again nothing to do with classes).
In other words, it’s a modification to the way a function is called. It does four things that I will quote directly from the YDKJS “this & object prototypes” book:
- a brand new object is created (aka, constructed) out of thin air
- the newly constructed object is
[[Prototype]]
-linked- the newly constructed object is set as the
this
binding for that function call- unless the function returns its own alternate object, the
new
-invoked function call will automatically return the newly constructed object.
We will get onto prototype linking later on in the course, and of course it’s also covered in the YDKJS book.
We can take any function in our program to hijack the call as a constructor call. It does everything that the function call normal does, but it does the above four things as well.
function foo() {
this.baz = “baz”;
console.log(this.bar + ” ” + baz);
}var baz = “bar”;
var baz = new foo(); //???
In this example we get this.bar returning as undefined because there is no this.bar. We are binding a brand new object to this. On the same line of code baz has no value either so is also undefined.
Order of precedence
We could have a call site that matches two or three of these different rules.
We see some code that proves the order of precedence in case you are a cynic and you need to satisfy yourself first. For everyone else, just remember these four questions:
-
Was the function called with ‘new’?
-
Was the function called with call or apply specifying an explicit this?
-
Was the function called via a containing/owning object (context)?
-
Default to global object (except strict mode)
We can infer from this that the new keyword is even able to override hard binding.
Quiz: this
Once again watch the course for the answers.
- What determines which object a functions’s this points to? What’s the default?
- How do you “borrow” a function by implicit assignment of this?
- How do you explicitly bind this?
- How can you seal a specific this to a function? Why do that? Why not?
- How do you create a new this?
Continue to Part 6 – Closure