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
undefined
ToString(primative)
:
ToString(primative)
null "null"
undefined "undefined"
true “true"
false "false"
3.14159 "3.14159"
0 "0"
-0 "0"
ToString(object)
:
ToString(object)
[] ""
[1,2,3] "1,2,3"
[null,undefined] ","
[[[],[],[]],[]] ",,,"
[,,,,] ",,,"
{} "[object Object]"
{a:2} "[object Object]"
{ toString(){
return "X";
}} "X"
ToNumber(primative)
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)
ToNumber(object)
[""] 0
[“0”] 0
[“-0”] -0
[null] 0
[undefined] 0
[1,2,3] NaN
[[[[]]]] 0
{ .. } NaN
{ valueOf() {
return 3;
} } 3
ToBoolean
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
undefined
true
If x is
undefined
null
true
If x is a
number
string
x == ToNumber(y)
If x is a
string
number
ToNumber(x) == y
If x is a
boolean
ToNumber(x) == y
If y is a
boolean
ToNumber(y)
If x is either a
string
number
symbol
object
x == ToPrimitive(y)
If x is an
object
string
number
symbol
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,
==
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
var
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
this
A function’s this
Will default to global
this
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 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
new
What happens when
new
- Creates a brand new empty object
- Links that object to another object
- Calls function with set to the new object
this
- If the function does not return an object, assume return of
this
How to determine
this
- Was the function called with ?
new
- the newly created object will be the keyword
this
- the newly created object will be the
- Is the function called by or
call()
?apply()
- 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
this
An arrow function does not define a
this
Objects are not scopes.
The
this
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’.