Deep Javascript Foundations v3

Kyle Simpson, You Don't Know JS

Purpose of the course: Javascript developers are the only professional programmers who don’t regularly read the spec. To truly understand how to do your job, understand the tool.

Undefined vs undeclared vs uninitialized

  1. Variable that’s never been initialized (uninitialized)
  2. Variable that’s been initialized but hasn’t been defined (undefined)
  3. Variable that’s never been created (undeclared)
null == undefined; // true null === undefined; // false

NaN

isNaN(“my son’s age”) // true OOPS! //ES6 workaround Number.isNaN(“my son’s age”) // false

NaN will occur in your code, if you’re not testing for it, bugs will happen.

-0

Negative zero is a real number in mathematics and so it should be treated as such in our programs.

let x = -0; x === -0; // true x.toString(); // 0 oops! x > 0; // false x < 0; // false Object.is(x, -0); // true Object.is(x, 0); // false

Exercise

Polyfill Object.is() without typeof. Hint:

  • NaN is the only value not equal to itself
  • Must deal with 0 and -0: 1 / 0 = Infinity

Coercion

Root of all coercion mistakes is “” => 0 in toPrimitive coercions.

Javascript implicitly performs automatic type coercion as needed.

Null
and
undefined
are coercively equal to each other and nothing else.

ToString(primative)
:

  • null "null"
  • undefined "undefined"
  • true “true"
  • false "false"
  • 3.14159 "3.14159"
  • 0 "0"
  • -0 "0"

ToString(object)
:

  • [] ""
  • [1,2,3] "1,2,3"
  • [null,undefined] ","
  • [[[],[],[]],[]] ",,,"
  • [,,,,] ",,,"
  • {} "[object Object]"
  • {a:2} "[object Object]"
  • { toString(){
    return "X";
    }} "X"

ToNumber(primative)

  • "" 0
  • “0” 0
  • “-0” -0
  • “ 009 " 9
  • “3.14159" 3.14159
  • “0.” 0
  • “.0” 0
  • “.” NaN
  • “0xaf" 175
  • false 0
  • true 1
  • null 0
  • undefined NaN

ToNumber(object)

  • [""] 0
  • [“0”] 0
  • [“-0”] -0
  • [null] 0
  • [undefined] 0
  • [1,2,3] NaN
  • [[[[]]]] 0
  • { .. } NaN
  • { valueOf() {
    return 3;
    } } 3

ToBoolean

Truthy Falsy

  • “foo” “”
  • 23 0, -0
  • { a:1 } null
  • [1,3] NaN
  • true false
  • function(){..} undefined

Equality

==
vs
===

==

If the types are the same,

return x === y

If x is

null
and y is
undefined
, return
true

If x is

undefined
and y is
null
, return
true

If x is a

number
and y is a
string
, return
x == ToNumber(y)

If x is a

string
and y is a
number
, return
ToNumber(x) == y

If x is a

boolean
, return
ToNumber(x) == y

If y is a

boolean
, return x ==
ToNumber(y)

If x is either a

string
,
number
,
symbol
, and y is an
object
, return
x == ToPrimitive(y)

If x is an

object
and y is a
string
,
number
, or
symbol
, return
ToPrimitive(x) == y

Else return

false

Avoid:

  1. ==
    with 0 or “” (or even “ “)
  2. ==
    with non-primitives
  3. ==
    with true or
    ==
    with false

Summary

Making types known and obvious leads to better code. If types are known,

==
is best.

Typescript and Flow

“I don’t use them because they fix my problems in a way that makes it worse”

Benefits

  • catch type-related mistakes
  • communicate type intent
  • provide direct IDE feedback
  • check coercion during operations on disperate types

Caveats

  • inferences is best-guess, not a guarantee
  • annotations are optional, defaults to any, a false sense of security
  • any untyped program is uncertain

Pros

  • makes types much more obvious
  • popular, so the invest to learn pays off longterm
  • type systems are very good at what they do

Cons

  • non-js-standard syntax, isn’t portable or ubiquitous
  • requires an extra step to the build process, making developers learn more as a barrier to entry
  • the sophistication can be intimidating and explodes in complexity

Summary

  • Javascript has a type system which uses various forms of coercion for value type conversion
  • avoiding an entire part of the language perpetuates bugs by failing to embrace features of the language
  • JS’s types can be learned and leveraged
  • adopt a coding style that makes types as obvious as possible
    • this is design better code that is more readable and robust

Scope

Where to look for things. What are we looking for? Identifiers. Values.

Four stages for a compiler:

  1. Lexing
  2. Tokenization
  3. Parsing - stream of tokens into abstract syntax tree
  4. Code generation

Lexical scope - the idea that scopes can exist within each other and it’s the job of the compiler to figure it out before runtime

target reference = identifier expression

source reference = !target

//non-strict mode topic = “React”; //creates a global `topic` variable
“use scrict” topic = “React”; //ReferenceError

undefined - a variable exists but at the moment it has no value

undeclared - never formally declared in any scope we have access to

When

let
hoists variables to its block scope it doesn’t initialize them to undefined like
var
does (uninitialized means the variable can’t be accessed and throws a TDZ error when attempted)

Function expressions

//unnamed expression var clickHandler = function () { // … }; //named expression var keyHandler = function keyHandler() { // … };

Why you should prefer named expressions:

  1. Reliable function self-reference
  2. More debugging stack traces
  3. More self-documenting code

Note: It’s problematic when you can’t come up with a name for a function. It’s probably doing more work than it should. If every function has a purpose, every function needs a name.

Immediately-Invoked Functional Expression (IIFE)

The parentheses makes the statement an expression instead of a definition so the function is invoked immediately.

(function keyHandler() { })();

Block scoping

var teacher = “Kyle”; { let teacher = “Suzy”; console.log(teacher); //Suzy } console.log(teacher); //Kyle

Closure

What is it?

Closure is when a function retains memory of its lexical scope even when the function is executed outside that lexical scope

Why is it so important?

It’s a necessary item of a lexically scoped language with first-class variables. It’s a preservation of a variable, not a snapshot of a value.

Modules

Encapsulated data with public and private functions and properties. State of a module is held by its methods via closure.

ES6 modules - singleton, single exported module per file

named import -

import ask from “workshop.mjs”

namespace import -

import * as workshop from “workshop.mjs”

Objects

this

A function’s

this
references the execution context for that call, determined entirely by how the function was called

Will default to global

this
if not implicitly or explicitly set

implicit binding

function ask(question) { console.log(this.teacher, question); } var workshop1 = { teacher: “Kyle”, ask: ask, }; var workshop2 = { teacher: “Suzy”, ask: ask, }; workshop1.ask(“May I?); workshop2.ask(“Please?);

explicit binding

Invoke the

ask
function in the context I specify

function ask(question) { console.log(this.teacher, question); } var workshop1 = { teacher: “Kyle”, }; var workshop2 = { teacher: “Suzy”, }; ask.call(workshop1, “May I?); ask.call(workshop2, “Please?);

Hard bound functions using

.bind()

setTimeout(workshop.ask.bind(workshop), 10, “Hard bound?);

new
keyword

What happens when

new
is in front of a function call?

  1. Creates a brand new empty object
  2. Links that object to another object
  3. Calls function with
    this
    set to the new object
  4. If the function does not return an object, assume return of
    this

How to determine

this
keyword?

  1. Was the function called with
    new
    ?
    • the newly created object will be the
      this
      keyword
  2. Is the function called by
    call()
    or
    apply()
    ?
    • the context object specified will be
      this
  3. Is the function called on a context object?
    • That object will be
      this
  4. Default to global except in strict mode

Arrow function and lexical
this

An arrow function does not define a

this
keyword, but it does resolve lexically

Objects are not scopes.

The

this
keyword is a context.

Prototypes

Class vs instance

  • class is the abstract pattern for a thing (e.g. blueprint)
  • an instance is when an architect builds from the blueprint (e.g. building)

A class system fundamentally copies but in javascript, that is not the case behind the scenes. A “constructor call” makes an object linked to its own prototype, not copying.

function Workshop(teacher) { this.teacher = teacher; } Workshop.prototype.ask = function(question){ console.log(this.teacher, question); }; var deeJS = new Workshop(“Kyle”); var reactJS = new Workshop(“Suzy”); deepJS.ask(“Is ‘prototype’ a class?); // Kyle Is ‘prototype’ a class? reactJS.ask(“Isn’t ‘prototype’ ugly?); // Suzy Isn’t ‘prototype’ ugly? deepJS.constructor === Workshop; deepJS.__proto__ === Workshop.prototype; // true Object.getPrototypeOf(deepJS) === Workshop.prototype; // true

Objects in javascript have a property named ‘prototype’ that points at an object and there is a property pointing back at the user’s object named ‘constructor’.