JavaScript: Advanced JS Foundations

I went to an all day tutorial by Kyle Simpson. He has a book called You Don't Know JS. Here are my notes:

Scope

I missed the first 2 hours. However, that only consisted of 14 or so slides, and I managed to catch up by reviewing the slides and someone else's notes.

JS is a compiled language. It continuously compiles and executes the code.
var foo = "bar"
is 2 operations not 1. It's both a declaration as well as an assignment.

The scope manager reserves spots in memory for variables.

When you assign a variable, if it's heard of it, it uses it, otherwise, it makes it global.

Functions have their own scopes.

He talked about shadowing.

Figuring out the scope of things is done during the compilation phase.

He talked about the LHS and RHS of an assignment.

window and global scope are different. Global scope sits above window. window is an alias to global scope only in the browser.

It's not a good idea to use global scope because:
  • You're more likely to see variable name classes.
  • Looking up something in the global scope is harder (slower?).
You should use strict mode.

"Undefined" means that a variable has been declared, but has not been assigned to yet. "Undeclared" means that a variable has not been declared, and there is not instance attached to it.

Strict mode takes away many operations that make your program slower.

If you execute a non-function as if it were a function, you'll get a runtime type error.

Formal function declarations start with the word "function" as the first thing in the statement. Hence, if you wrap a function in parenthesis, that's a function expression, not a function declaration.

Here's another function expression (not a function declaration):
var foo = function bar() {}
Function declarations bind to their enclosing scope.

A named function expression should be preferred to an anonymous function expression.

Lexical scope is something that is determined at author time, and is thus fixed at runtime.

Dynamic scopes are determined at runtime.

"Scope" is where to look for things like variables and function declarations.

JavaScript mostly uses function scope.

However, when you use try {} catch (err) {}, err is actually block scoped. You can't access it outside the catch block.

He showed lexical scope as a series of concentric rings.

Named functions can refer to themselves using their name.

3 reasons why named function expressions are preferable over anonymous function expressions:
  1. You sometimes need to refer to the function's name within the function. This is useful for unbinding event handlers. Note, arguments.callee has been deprecated.
  2. Anonymous functions don't have a name, and hence they make stack traces a lot less readable.
  3. If you receive a function object, you can look at the name to know a little about what it is and who's sending it.
The only reason to not name functions is if "you're lazy, uncreative, or you're trying to save space in your slides".
He thinks arrow functions suck, one of the reasons is that they're anonymous. The only thing he likes about them is that they fix lexical this.

In the newest JavaScript standards, they're adding a new feature called name inferencing. JavaScript can now infer the name of this function:
var f = () => 1;
However, callbacks don't have name inferencing, and that's the one time where you're most likely to use an arrow function.

He prefers named functions over arrow functions.

He says he's big into readability.

He uses arrow functions only when he needs to maintain lexical this. He says it's better than var self = this.

Only 2% of his code uses lexical this.

Linters are useful, but only if you know what they're for. Linters check code style. They can't check for correctness, accuracy, etc.

He doesn't think any linter understands lexical scope.

He says that linters are great in theory, but he ends up turning off a lot of rules because the linter is not smart enough.

He wishes the tools were smarter.

He says that linters are a bit stupid with respect to hoisting as well.

He thinks of lexical scopes as nested bubbles.

Lexical scope is fixed by the developer. It doesn't change dynamically.

You can use eval to add new variables to a scope dynamically:
function f(s) {
    eval(s);
}

f('var x = 1;');
This is an exception to the normal rule that scopes are determined statically. The way to think about it is that eval executes the code as if you had written it in the place where you call eval. Using eval inhibits the VM from making certain optimizations.

He doesn't think that the security argument is a good argument against using eval. He says that your system can be insecure even if you don't use eval. Hence, he doesn't think that eval is inherently insecure. He doesn't like eval because it is horrible from a performance point of view.

If you have to ask whether you should use eval or not, the answer is no. You should only use it if you already know what you're doing and the tradeoffs.

He says he's a compiler nerd.

You can use a function constructor instead of eval.

The valid reasons for eval are extremely esoteric.

One reason is if you're using eval to figure out at runtime what syntax is supported by the engine.

You should probably still use the function constructor instead of eval.

In strict mode, eval creates its own scope. However, even in strict mode, the performance is still severely impacted by eval.

He says let and const are not the solution to var.

He says that browsers use a regex to look for eval, and they it sees it, they disable the performance optimizations for that one file.

The with keyword is the other way of messing with lexical scoping in a dynamic manner.

with looks like a syntactic shortcut. It turns an object into a lexical scope at runtime. That's slow, and it has some subtle problems. You shouldn't use with.

He says one of the worse sins in programming is to create a construct that you don't know what it's going to do until runtime. (What about if statements?)

If you use:
with (obj) {
    d = 3;
}
Then if obj has d, it'll set that. Otherwise, it'll look up the scope hierarchy for a d. He says that that's horrible because you don't know what it's going to do until runtime.

You cannot use the with keyword in strict mode.

Knockout.js uses the with keyword all over the place. Function constructors don't run in strict mode. They make use of that.

The Chrome console wraps all of your code in a with statement in order to provide a bunch of stuff that's only available in the console.

The with statement makes your code run slower.

Next, he talked about properties when it comes to lexical scoping.

An IIFE is an immediately invoked function expression:
(function() {
    ...
})();
He prefers to give his IIFEs names. If you can't come up with a name, call it IIFE.

This idiom also works:
!function IIFE() {
    ...
}();
The negation doesn't really do anything except turn the function declaration into a function expression.

He prefers:
void function IIFE() {
    ...
}();
The void operator just turns the function into a function expression. Furthermore, the void is kind of a hint that the function has no return value.

He prefers void as a stylistic preference, but most people use parenthesis.

It's bad to have a bunch of global stuff.

He talked about the "principle of least exposure" or "principle of least privilege". You should protect everything by default and expose things only when necessary.

It's very common to put your entire file into an IIFE.

He's working his way toward module systems.

You could just use this idiom to expose things from within the IIFE:
(function() {
    window.foo = ...;
})();
Now, elsewhere in the codebase, people can just use foo().

Here's another idiom:
(function(global, $) {
    global.foo = foo;
})(window, jQuery);
Now, you can use $ to reference jQuery even if you have multiple libraries that each like to use $.

You could name your IIFE something like CustomerLogin. (It's strange to see him use this naming convention since that convention is usually reserved for constructor functions.)

Variables declared using var are scoped within the nearest enclosing function.

let enables you to scope a variable to a block instead of a function.

He says that if you're not using let, putting var inside a block is still useful as a stylistic signal that the variable isn't used outside the block even though variables are hoisted to function scope.

However, using let is even better.

He thinks the idea of replacing all vars with lets is really stupid.

He thinks that let doesn't replace var, it augments it.

If he wants function scope, he uses var. If he wants block scope, he uses let.

He doesn't recommend using const very much. He doesn't think it really helps make anything better.

He is not consistent as to whether he puts a space before the curly when defining a function.

In general, he has very strong opinions and doesn't seem to have much regard for other people's opinions, even members of the standards bodies. Even the name of his book, "You don't know JS" presupposes he knows more than you.

He would have preferred this syntax for let since it's more explicit:
let (tmp = x) {
    x = y;
    y = tmp;
}
He likes this idiom:
if (x > y) {
    { let x = tmp;
        x = y;
        y = tmp;
    }
}
He likes it because it's clearer, and he's okay with the extra level of indentation.

The let keyword is good for for loops:
for (let i=0; i<10; i++) {
    ...
}
Dynamic scope is dependent on runtime conditions.

He says he doesn't know why most languages prefer lexical scoping over dynamic scoping other than for performance reasons. (This was explained in SICP. Lexical scoping makes it easier to reason about the program.)

You can think about hoisting by imagining that all the variable declarations and function declarations get moved to the top of the function. It's an important feature.

He says it enables mutually recursive functions, and that you wouldn't be able to use mutually recursive functions without function hoisting. (I think that as long as you have first class functions, you can do mutual recursion.)

Function expressions don't get hoisted.

Function hoisting is quite helpful. He routinely uses functions before they're defined.
foo();

function foo() {}
He likes to put the executable code at the top and the functions at the bottom.

If you try to use a let variable before it's been declared, you get a TDZ (temporal dead zone) error. I think they may call it a ReferenceError.

He recommends that you put all the let declarations all on the first line.

Closure


Closure is when a function "remembers" its lexical scope even when the function is executed outside that lexical scope.

He claims that JavaScript is the first "mainstream" (i.e. non-academic) language to have closures. I mentioned that Perl probably got them first and was mainstream at the time. He claims that Perl wasn't mainstream by the time JavaScript got them. As far as I understand it, Perl added my in version 5 which was released in 1994, and Perl was certainly mainstream by then.

Furthermore, I would also take issue with his claim that Lisp (which had closures) wasn't mainstream since various Lisp variants were certainly used outside of academia.

He talked about the problem that occurs when you generate closures from inside a for loop. He talked about how to use IIFEs to avoid this problem.

Here's the module pattern:
var foo = (function() {
    var o = { bar: 'bar' };
    return {
        bar: function() {
            console.log(o.bar);
        }
    };
})();

foo.bar();
Similarly:
var foo = (function() {
    var publicAPI = {
        bar: function()...,
        baz: function()...
    };
    return publicAPI;
})();
That way, the different things in publicAPI have access to each other via the name publicAPI.

He likes to criticize the TC39 committee.

He mentioned HTTP 2.

He said that if you want to use ES6 import syntax you should use HTTP 2 :-/ I think he may be glossing over how transpilers like Webpack fit into the picture.

Export syntax:
export function bar() { ... }
Import syntax:
import { bar } from "foo";
import * as foo from "foo";
He said there are about two dozen variations of the import / export syntax.

He hasn't switched to ES6 modules because there's political upheaval around them.

Even though the import syntax is valid in some browsers, the loader necessary to load them isn't yet a standard.

He said that TC39 didn't consider whether the Node guys would be able to switch to ES6 syntax.

He said the Node community isn't going to implement ES6 import syntax.

He says he's still on the sidelines.

He said the import syntax is synchronous.

Two characteristics that makes something the module pattern:
  • There has to be an enclosing function that executes at least once.
  • That function has to return something that has a function that closes around its outer scope.
He said the module pattern is the most important pattern in JavaScript.

Object-oriented Programming


He doesn't think JavaScript has classes, and you should not do anything like classes in JavaScript.

He created the term "OLOO" which stands for objects linked to other objects.

He says that "behavior delegation" better matches what JavaScript does.

Every function, when executing, has a reference to its execution context, which it calls this.

(He uses Sublime Text.)

4 ways to bind this:
  1. Implicit binding: If none of the other rules apply: if we're in non-strict mode, this refers to window. In strict mode, this is undefined.

    By the way, you almost never want this to refer to the global object.

  2. Default binding: If there is a context object at the call site (e.g. obj.f()), then use the context object.

    By the way, you can "borrow" functions by attaching them to the object and then on that object.

  3. Explicit binding: f.call(obj).

    This allows us to define a single function and then use it in multiple contexts.

    The this system is very dynamic whereas lexical scoping is very fixed.

    This breaks when you have code like this:

    $("#btn").click(obj.foo);

    It's because jQuery saves obj.foo as something like cb, and cb doesn't reference obj anymore. (JavaScript doesn't have implicit bound methods like Python does.) However, you can use explicit binding:

    $("#btn").click(obj.foo.bind(obj));

    jQuery has a proxy method that does something similar.

    Some people just abandon using this entirely and just bind things explicitly since it gives you predictability back. However, hard binding is less flexible.

    The this keyword is very powerful and very dynamic. Sometimes, lexical scoping is simpler and more predictable.

  4. Using new: new foo();
There's a precedence order for the 4 rules, because it's possible to hit multiple rules at the same time:
  1. Was the function called with new?
  2. Was the function called with call or apply with an explicit this?
  3. Was the function called via a containing/owning object (i.e. context)?
  4. Otherwise, use the global object (or undefined in strict mode).
When you use new:
  1. A brand new empty object is created.
  2. The brand new empty object gets linked to another object (the prototype).
  3. The newly created and linked object gets passed into the function as this.
  4. If the function doesn't return an object, it assumes that the function should return this.
You can use new with almost any function. He says that constructor functions really don't do much and really don't construct anything. It's new that does all the work.

The new keyword can override even a hard-bound function (i.e. one that already has its own this).

Every single "object" is built by a constructor call. (I wonder if he considers an object literal to be a constructor call.)

A constructor makes an object prototype its own.

He explained the prototypal system.

He said the traditional approach to OOP (e.g. classes) is all about "copying" stuff from the parent into the child.

Pretending JavaScript is like other languages causes a lot of confusion.

Rather than copy down, it's a link up.

An object has a property that refers to another object.

The base prototype doesn't have a good name. It has things like toString().

The prototype object has a property called constructor. That doesn't necessarily mean that the thing it points to is really a constructor.

An object has a prototype. That prototype might refer to another prototype.
obj.__proto__ === ObjConstructor.prototype
obj.constructor === ObjConstructor
If you ask for something on this that isn't on this, it looks it up on the prototype.

A core theme of this class is how to compare and contrast JS's lexical scoping system with its prototypal system.

He's really big on the idea that functions aren't really constructors; they're just initializers. new does the real work of constructing the instance.

There's Object.getPrototypeOf(obj).

In the IE8 days, we used to write this hack:

obj.costructor.prototype

Both the constructor and prototype properties are changeable at runtime.

JavaScript didn't originally have super. ES6 has a super keyword.

It's not easy and automatic for a method in a child class to refer to the method of the same name in its prototype:
Foo.prototype.method.call(this)
If the child and parent have differently named methods, it's way easier.

To link from a child to a parent:
function Bar(who) {
    Foo.call(this, who);
}
Bar.prototype = Object.create(Foo.prototype);
All the magic of the prototype system really comes down to the fact that objects are linked together.

He said it's too complex. He doesn't like "classes" in JavaScript.

He prefers thinking of it as "behavior delegation" over "inheritance".

OLOO = objects linked to other objects

He likes this idiom. He calls this OLOO style coding:
var Foo = {
    init: function(who) {
        this.me = who;
    }
};
var Bar = Object.create(Foo);
Bar.speak = function() {
    ...
};
var b1 = Object.create(Bar);
b1.init("b1");
b1.speak();
This does the same as the other way of writing the code.

He likes the fact that Douglas Crockford created Object.create. However, he thinks that Crockford "went off the rails" when abandoning this.

In his code:
  • 95% = module pattern
  • 5% = delegation pattern (OLOO)
Object.create was added in ES5. Crockford suggested it. Here's how it's basically implemented:
Object.create = function(o) {
    function F() {}
    F.prototype = o;
    return new F();
};
Simpson's entire OO system is just based on Object.create.

There was some discussion at the end. He said that JavaScript's object system was radically different than any other language's since:
  • It's less about classes copying things into the object and more about an object pointing to its prototype.
  • Constructor functions don't actually construct instances, they only initialize them.
I pointed out that actually:
  • The prototype system started with Self in the 80's. Python's class system is very similar to JavaScript's if you treat classes as objects and never instantiate them. Each parent class is like the child class's prototype. When you look for something on a class, it'll go up the parent chain dynamically.
  • Java's constructor methods don't instantiate objects either. The constructor methods themselves are really just initializers like in JavaScript. By the time they're called, the object already exists. Objective C is one of the few OO languages that I know that lets you explicitly allocate and initialize the object in two different steps.
He also said that JavaScript and Lua are the only languages that allow you to just create an object and start attaching things to it without creating a class first. However, I think you can certainly do this in Python and Ruby, and I suspect you can do it in some other languages as well.

Delegation Design Pattern


"Delegation-oriented design".

He says that it was the late 90s when we finally started preferring composition over inheritance.

He says the prototypal chain is "virtual composition".

He says with delegation, it's peer-peer.

He says when you're writing JS, you should go with the flow and use JS the way it was meant to be used.

He says you should ignore the ES6 class syntax. He says that's the only part of the language that he categorically discards. He doesn't even touch the class keyword. He says it's duck tape on top of duck tape.

He doesn't think that class is just syntactic sugar. He says that's not true.

He says they're adding even more class-oriented stuff (doubling down) in later versions of JavaScript.

Anything you put on the prototype is public. It's not like closures where there's a lot of room to hide things.

Some parts of your app should use modules. Some parts should use the prototype system.

Here's an idiom he likes:
var Button = Object.assign(Object.create(Widget), {
    configure: ...
    activate: ...
});
He said to avoid shadowing method names in different parts of the prototype chain.

He said he's been working in JavaScript for 18 years.

Comments

Rob Galanakis said…
I have no idea how you stay so calm and collected in the face of such pompous nonsense. This author seems to validate a lot of the problems I see in JS attitudes...
Shannon Behrens said…
Rob, thanks for the compliment ;)
Fred said…
>He created the term "OLOO" which stands for objects linked to other objects.

Oh man, you know they really mean business when they start bringing in their own terms! Lol.

"Yea so I made this term, no one else uses it and it hasn't really caught on but it totally will!"

Sure buddy.
Shannon Behrens said…
> "Yea so I made this term, no one else uses it and it hasn't really caught on but it totally will!"

That's pretty funny ;)