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
- Variable that’s never been initialized (uninitialized)
- Variable that’s been initialized but hasn’t been defined (undefined)
- 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" 175false 0true 1null 0undefined 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] NaNtrue falsefunction(){..} 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:
==with 0 or “” (or even “ “)==with non-primitives==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:
- Lexing
- Tokenization
- Parsing - stream of tokens into abstract syntax tree
- 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:
- Reliable function self-reference
- More debugging stack traces
- 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?
- Creates a brand new empty object
- Links that object to another object
- Calls function with
thisset to the new object - If the function does not return an object, assume return of
this
How to determine this keyword?
- Was the function called with
new?- the newly created object will be the
thiskeyword
- the newly created object will be the
- Is the function called by
call()orapply()?- the context object specified will be
this
- the context object specified will be
- Is the function called on a context object?
- That object will be
this
- That object will be
- 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’.
