Welcome to Part 3 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
Part 7 – Object Prototypes
Lexical Scope
We are currently about a third of the way through the Scope module of the course. The material we have covered broadly matches the content covered by the first chapter of the Scope & Closures book.
The material in this part of the course broadly matches that of:
Chapter 2 – Lexical Scope
Chapter 3 – Function vs. Block Scope from Scope and Closures and
Chapter 2: Syntax from ES6 & Beyond
Function Declarations, Function Expressions, and Block Scope
var foo = function bar() {
var foo = “baz”;function baz(foo) {
foo = bar;
foo; // function …
}
baz();
}foo();
bar(); // Error!
We’ve seen function declarations used earlier on in this course. The first line of the above code sample is a function expression.
Kyle says this statement is not a function declaration because the function keyword is not the very first word in the statement.
He talks about anonymous function expressions e.g.
var foo = function() {
//any code
}
Kyle says function expressions are great. He gives 3 reasons why named function expressions are better than anonymous ones:
- When you have an anonymous function, we have no way inside of the function to refer to ourselves. We do with a named function.
- Anonymous functions don’t play well in debugging. If we get an error in production code, you might find yourself debugging minified code. With anonymous functions the stack trace you’ll see will be very frustrating and not very helpful.
- It self documents code
(See Chapter 3 Anonymous vs. Named from the Scope & Closures book. Eric Elliot also has a good explanation here)
Back to the main example:
var foo = function bar() {
//any code
}
Unlike a function declaration, bar does not get declared in the outer scope
Kyle also says most JavaScript developers don’t know that as of ES3 when try catch was added to the language, we have had block scoping in JavaScript (it’s been around a long time before ES6)
The catch clause is block scoped: we can only reference the error object inside this block.
He also says linters don’t tend to be intelligent enough to deal with repeated catch blocks correctly. Kyle turns off the linting rule.
Lexical Scope
There are two predominant models for scoping:
The vast majority of languages deal only with lexical scope.
Lexical scope means compile time scope: at the time the code is compiled, the decisions for how all the scoping are going to occur were made in stone.
That’s exactly what we’ve seen in the earlier code examples.
Kyle has an introduction to dynamic scope in Appendix A of Scope & Closures, and covers it in depth in this & object prototypes.
We see an image of two buildings, one representing lexical scope, and a similar looking one representing dynamic scope. Kyle asks us to ignore dynamic scope for now as it’s covered later on in this course.
Metaphorically, we take the stairs or the elevator up to the next level whenever we don’t find a variable in the current scope, until we either find what we’re looking for or we reach the top level representing the global scope.
That’s one way of thinking about it. Another way is to think in terms of scope bubbles.
Kyle says modern compilers actually have optimizations in place to fetch from the correct scope immediately. But the mental exercise of looking for scope one level at a time can be useful in helping us to understand the JavaScript processes.
Cheating Lexical Scope: eval
An eval statement takes a string and treats the contents of that string as if it were JavaScript code.
var bar = “bar”;
function foo(str) {
eval(str); //cheating!
console.log(bar); //42
}foo(“var bar = 42;”);
This code cheats by modifying what would have been a lexical decision at runtime.
Many people like to pronounce eval as evil. In this lesson Kyle goes through the reasons for this:
- code with an eval statement runs slower because the lookup optimizations aren’t possible anymore
- But if strict mode is on, a new scope is created for the eval statement, allow optimizations to be done.
Kyle is probably now regretting offering a public service saying not to use the eval statement! If you have to ask, don’t use it!
There are a few niche cases where it may be needed, but in general, don’t use it.
Even worse than eval is the with keyword.
We see a code sample explaining the use of with – we want to stop using the object reference over and over again. It is often used in golfing competitions.
We see one of the problems you can run into when using it:
When we want to create a new property on the object that with is being applied to, it doesn’t work as we want it to. It leaks up to the outer scope!
Kyle explains that worse still is it creates a whole new lexical scope at runtime.
The with keyword is thankfully disallowed in strict mode.
IIFE Pattern
var foo = “foo”;
(function(){
var foo = “foo2”;
console.log(foo);
})();console.log(foo);
Here we want to hide a couple of statements in a new scope and we do it using an immediately invoked function expression.
The advantage of this instead of creating a new function and invoking it is we don’t leak a new name onto the enclosing scope.
Remember we learned that whenever the word function is not the first word in the statement, we are creating a function expression instead of a function declaration. Wrapping parentheses around it is the most terse way to achieve this.
We can also execute it right way by adding another set of parentheses on the end of it.
The JavaScript language was influenced by Scheme and we see one of those influences here.
Kyle credits Ben Alman for coining the name for this pattern and says he’s a really smart guy.
One of the audience questions is whether we should name our IIFEs? Kyle recommends this so that we don’t have anonymous functions in our stack trace.
A variation is to pass things into it:
var foo = “foo”;
(function(bar){
var foo = bar;
console.log(foo); //”foo”
})(foo);console.log(foo); // “foo”
In real word coding we don’t name things foo and bar. Kyle uses the pattern to alias the window object as global to make it extra clear that the variable is global.
Another common use is renaming $ so that the jQuery $ does not conflict with another use of it in another library.
(function($){
//jQuery code
})(jQuery);
One audience member says he’s seen a second argument of undefined used. Kyle thinks this is overkill and we should just use strict mode instead.
IIFE Questions
Firstly Kyle begins to introduce ES6 block scope with the let keyword.
However there’s a question from Derek W:
“Is there any difference if you put the invoking parentheses inside or outside the expression parentheses?”
Douglas Crockford has previous talked about dog balls – he doesn’t like seeing the invoking parentheses on the outside!
This is basically a stylistic choice for you to decide for yourself, but Kyle presents both sides of the argument here. Pick one and make it a standard for your team.