Javascript: The Hard Parts, v2
Introduction
The link to the course document is here
The instructor breaks down the difference between Junior, Mid and Senior-Level developers, and the purpose of the course for the different levels of developers.
JavaScript Principles
Thread of Execution
When JavaScript runs, it:
-
Goes through code line-by-line and runs/’executes’ each line - known as the thread of execution
-
Saves ‘data’ like strings and arrays so we can use that data later - in its memory
-
We can even save code (functions)
Memory is the place in which we store data.
Functions
A function being run is like a miniature program. We need the code and the memory to run a function.
A function is code we save (‘define’) functions & can use (call/invoke/execute/run) later with the functions name & ()
Execution context is created to run the code of a function. It has 2 parts:
-
Thread of execution
-
Memory
The first thing that happens when a function is run is a new execution context is established and the parameters and their arguments are stored in local memory. i.e. inputNumber: 3
JavaScript contains a single thread of execution, which means that you can only do one thing at a time (unless you are running your code asynchronously).
Call Stack
-
JavaScript keeps track of what function is currently running. i.e. where is the thread of execution
-
Run a function - add to call stack
-
Finish running the function - JS removes it from the call stack
-
Whatever is at the top of the call stack is the function that is currently being run
Functions & Callbacks
Generalized Functions
The above function is not generic enough, if we wanted to square other numbers, we would have to rewrite the function for each number we would want to square.
Don’t Repeat Yourself!
How could we make the function more generic?
With the above function, you could now pass any number and it would return the square of that number num*num
Generalizing functions:
‘Parameters’ (placeholders) mean we don’t need to decide what data to run our functionality on until we run the function
- Then provide an actual value (‘argument’) when we run the function
Higher order functions follow the same principle
- We may not want to decide exactly what some of our functionality is until we run our function
Repeating Functionality
This lesson walks through a function, further driving home the points above about execution context and return values being passed to global memory.
Higher Order Functions
A better, more general, way to implement a function could be:
Higher Order Functions Example
The example used in the section is the above code block. Just walking through each step of the execution context from global through the call stack as it is updated by calling functions.
Higher Order Functions Q&A
No notes
Callbacks & Higher Order Functions
Functions in JavaScript are first class objects. They can coexist with and be treated like any other JavaScript object:
-
Assigned to variables and properties of other objects
-
Passed as arguments into functions
-
Returned as values from functions
A callback function is a function that is placed inside of a Higher Order Function.
A Higher Order Function is defined as: A function that takes in OR passes out (returns) a function.
Callbacks and Higher Order Functions simplify code and keep it DRY
-
Declarative, readable code: Map, filter, reduce - the most readable way to write code to work with data
-
Asynchronous JavaScript: Callbacks are a core aspect of async JavaScript and are under the hood of promises, async/await
Arrow Functions
Arrow functions are a shorthand way to save functions
Under the hood, JavaScript will put in parenthesis, curly brackets, and a return statement if your function only has one return.
Anonymous and arrow functions
-
Improve the immediate legibility of code
-
…but they are merely ‘syntactic sugar’
-
Understanding how they work ‘under the hood’ is vital to avoid confusion
Pair Programming
Pair programming is the most effective way to grow as an engineer, because:
-
You get to tackle blocks (issues) with a partner
-
Stay focused on the problem
-
Refine technical communication
-
Collaborate to solve the problem
Closure
Closure Introduction
-
Closure is the most esoteric of JavaScript concepts - If you really understand it, you understand JavaScript well
-
Enables powerful ‘pro-level’ functions like ‘once’ and ‘memoize’
-
Many JavaScript design patterns including the module pattern use closure
-
Build iterators, handle partial application, and maintain state in an asynchronous world
Every time a function gets invoked, it creates a new store of memory (in its execution context)…but
Functions with memories!
-
When our function gets called, we create a live store of data (local memory/variable environment/state) for that functions execution context
-
When the function finished executing, its local memory is deleted (except for the returned value)
-
But what if the function could hold on to live data between executions?
-
This would let function definitions have an associated cache/persistent memory
-
But it all starts with returning a function from another function
Returning Functions
Returning a function from another function
Nested Function Scope
Calling a function in the same function call as it was defined:
Retaining Function Memory
In the above function definition, when myNewFunction
is assigned to outer()
, the inner function incrementCounter()
is saved to it, including the parent scope of outer()
, which allows access to the variable counter
.
Function Closure
Continuing on with the function defined above in the ‘Nested Function Scope’ section, more about calling a function outside of the function call it was assigned is discussed.
Closure Q&A
If you define variables within the parent function and they are not referenced in the child function, does memory retain those variables? No, the child function will only retain (in global scope) those variable(s) which are directly referenced within the child function definition. Otherwise, there would be memory leaks.
Closure Technical Definition & Review
Recommended reading: ‘If Hemingway Wrote JavaScript’
Where you save your function determines what data it will have access to when it is run.
Persistent Lexically Scoped Referenced Data (P.L.S.R.D) is the term used to describe a child functions access to its parent scope, AKA closure.
Multiple Closure Instances
If you store a returned function definition in a variable, you are creating unique instances and (future; at run-time) execution contexts for that function. Following the above example, if you set var anotherVariable = outer();
, anotherVariable would have its own execution context separate from myNewFunction.
Practical Applications
Closure gives our functions persistent memories and an entirely new toolkit for writing professional code
-
Helper functions: Everyday professional helper functions like ‘once’ and ‘memoize’
-
Iterators and generators: Which use lexical scoping and closure to achieve the most contemporary patterns for handling data in JavaScript
-
Module pattern: Preserve state for the life of an application without polluting the global namespace
-
Asynchronous JavaScript: Callbacks and Promises rely on closure to persist state in an asynchronous environment
Closure Exercises
Asynchronous JavaScript
Single Threaded Execution Review
Promises, Async, and the Event Loop
-
Promises - The most significant ES6 feature
-
Asynchronicity - The feature that makes dynamic web applications possible
-
The Event Loop - JavaScript’s triage
-
Microtask queue, Callback queue, and Web Browser features (APIs) - here’s an article that goes into further detail
JavaScript is a synchronous language. This means that only one task can be run at a time, or only one line of code is executed at a time.
Asynchronicity in JavaScript
Asynchronicity is the backbone of modern web development in JavaScript yet…
JavaScript is:
-
Single threaded (one command runs at a time)
-
Synchronously executed (each line is run in the order it appears in the code)
So what if we have a task:
-
Accessing Twitter’s server to get new tweets that takes a long time
-
Code we want to run using those tweets
Challenge: We want to wait for the tweets to be stores in tweets so that they’re there to run displayTweets on - but no code can run in the meantime
JavaScript is not enough - we need new pieces (some of which are not JavaScript at all)
Our core JavaScript engine has 3 main parts:
-
Thread of execution
-
Memory / Variable Environment
-
Call stack
We need to add some new components:
-
Web Browser APIs / Node background APIs
-
Promises
-
Event Loop, Callback / Task queue, and micro task queue
Asynchronous Browser Features
ES5 Solution: Introducing Callback Functions and Web Browser APIs
Web Browsers have Dev Tools (Console), Sockets, the ability to make Network Requests, and they can Render the HTML DOM (Document Object Model). We cannot (in JavaScript) code for these directly, but JavaScript can interface with them. JavaScript has ‘labels’ for each of the web browser ‘features’, that allow JavaScript to interact with them. i.e. setTimeout can use the browsers ‘timer’ function. ‘document’ is another web browser feature that allows JavaScript to interact with it.
Web API Example
This section is focused on setTimeout which is not a feature of JavaScript, but a way for JavaScript to interface with a web browser(s) timer
API - more details on MDN
setTimeout
takes a number of arguments, but this focused on passing in a function and a number which represents the amount of time, in milliseconds, before the function will run i.e. setTimeout(myFunction, 1000)
setTimeout
will start a timer at 0s and when it meets the argument 1000(ms)
, it will invoke the function myFunction
Web API Rules
This section quickly talks about there being rules between JavaScript and how the browser interacts with it. They are defined as being quite strict and the following JavaScript was displayed as an example to consider:
What will happen?!
Callback Queue & Event Loop
Walking through the above JavaScript, this is what happens:
- Define function
printHello
- Define function
blockFor1Sec
- Run
setTimeout
Browser Feature, passing in theprintHello
function with0ms
passed in as the time argument. This adds theprintHello
function into the Callback Queue - Call
blockFor1Sec
function - The
console.log
prints - Finally, the
printHello
function is executed
The Callback Queue is not allowed to run all regular code is run first. JavaScript checks if the Call Stack is empty before checking the Callback Queue. This is known as the Event Loop.
Callback Queue & Event Loop Q&A
Q: Does the Event Loop constantly run?
A: Yes, the Event Loop is constantly running for the duration of the applications lifecycle
Callback Hell & Async Exercises
ES5 Web Browser APIs with Callback Functions
Problems:
-
Our response data is only available in the callback function - Callback Hell (where we are doing all of our work on some data inside of a function)
-
Maybe it feels a little odd to think of passing a function into another function only for it to run much later
Benefits:
- Super explicit once you understand how it works under-the-hood
Promises
Promises Introduction
ES6+ Solution (Promises)
Using two-pronged ‘facade’ functions that both:
-
Initiate background web browser work
-
Return a placeholder object (promise) immediately in JavaScript
In ES6, fetch
is the replacement for xhr
, but unlike xhr
, fetch
(referred to above as a ‘two-pronged’ facade function) will both initiate a Web Browser function and in the JavaScript environment create a placeholder object called a promise object
. When the network request completes, the object will be filled in with the result. The Web Browser function and the placeholder object are intrinsically linked.
ES6+ Promises
Promises Example: fetch
In the example above, the following is happening:
-
Declare a function
display
-
Declare a constant
futureData
and store the result offetch(...)
. This will immediately return a ‘Promise Object’ with two properties; {value: undefined, onFulfilled: []} and store it in thefutureData
constant. The other consequence offetch(...)
will be in the Web Browser in the form of a Network Request. The Network Request requires the URL and path, which is defined. The Network Request defaults to aGET
request, but you can pass in (as an argument) another type of request, i.e.POST
. The response from the Network Request gets stored in thefutureData
object that was created, specifically in thevalue
property of that object.
Promises Example: then
-
The
onFulfilled
array in thefutureData
object contains a number of [hidden] methods (which are ‘directly’ inaccessible), but can be accessed with ‘key’ words, in this casethen
. passing thedisplay
function intofutureData.then()
will automatically pass the content offutureData.value
into thedisplay
function defined on the first line. -
The
console.log('Me first!')
will finally run and printMe first!
in the console. -
The Network Request has successfully completed and the response is stored in
futureData.value
. This will trigger the...then(display)
and pass infutureData.value
as the argument for thedisplay
function. A new execution context is created for thedisplay
function.futureData.value
will be stored asdata
in thedisplay
function.
Web APIs & Promises Example: fetch
It is important to understand how promise-deferred functionality gets back into JavaScript to be run
The then
method and its functionality to call on completion
-
Any code we want to run on the returned data must also be saved on the promise object
-
Added using the
.then
method to the hidden propertyonFulfillment
-
Promise objects will automatically trigger the attached function to run (with its input being the returned data)
Asynchronous JavaScript is defined as executing code out of the order in which it was written.
Web APIs & Promises Example: then
Walking through the above JavaScript block, the .then
eventually ‘GET(s)’ data back from its request and…
Web APIs & Promises Example: Microtask Queue
When finally, all of the code in the global scope has executed, the Event Loop checks the Callback and Microtask Queue(s) for additional tasks waiting to be run. fetch
placed the Network Request into the Microtask Queue, which is where the Event Loop first checks for tasks, which then triggers the .then
. Finally, the Event Loop is able to reach the Callback Queue which contains the setTimeout
. So while the setTimeout
function was called first, it in fact gets run last because of the order of the Call Stack, Event Loop, Microtask Queue, and Callback Queue(s). Any function that is attached to a promise object gets stored in the Microtask Queue, which is the first Queue that the JavaScript Event Loop checks, before the Callback Queue.
Promises and Asynchronous Q&A
Specific to this section, the WorkerGlobalScope Web API contains fetch
.
The Microtask Queue, once the Event Loop enters it, will continue to run any functions that are waiting in the queue until there are not functions left to run.
Any function that is attached to a promise object gets pushed into the Microtask Queue, any function that is directly tied to a Browser Feature or returns a function, gets pushed into the Callback Queue.
Promises Review
Problems:
-
99% of developers have no idea how they’re working under the hood
-
Debugging becomes super-hard as a result
-
Developers fail technical interviews
Benefits:
-
Cleaner readable style with pseudo-synchronous style code
-
Nice error handling process
We have rules for the execution of asynchronously delayed code
-
Hold promise-deferred functions in a microtask queue and callback function in a task queue (Callback queue) when the Web Browser Feature (API) finishes
-
Add the function to the Call stack (i.e. execute the function) when:
-
The Call stack is empty AND all global code has run (the Event Loop checks this condition)
-
Prioritize functions in the microtask queue over the Callback queue
Promises, Web APIs, the Callback & Microtask Queues, and Event Loop enable:
-
Non-blocking applications: This means we don’t have to wait in the single thread and don’t block further code from running
-
However long it takes: We cannot predict when our Browser feature’s work will finish so we let JavaScript handle automatically running the function on its completion
-
Web applications: Asynchronous JavaScript is the backbone of the modern web - letting us build fast ‘non-blocking’ applications
Classes & Prototypes
Class & OOP Introduction
Classes, Prototypes - Object Oriented JavaScript
-
An enormously popular paradigm for structuring complex code
-
Prototype chain - the feature behind-the-scenes that enables emulation of OOP but is a compelling tool in itself
-
Understanding the difference between
_proto_
andprototype
-
the
new
andclass
keywords as tools to automate our object and method creation
One of the most popular interview questions for mid to senior-level developers is what is the keyword new
doing under-the-hood
Core of development (and running code)
-
Save data (e.g. in a quiz game the scores of user1 and user2)
-
Run code (functions) using that data (e.g. increase user2’s score)
So what?!
In a quiz game you need to save lots of users, but also admins, quiz questions, quiz outcomes, league tables - all have data and associated functionality
In 100,000 lines of code…
-
Where is the functionality when I need it?
-
How do I make sure the functionality is only used on the right data?
Code should be:
-
Easy to reason about - Easy to figure out what is going on
-
Easy to add features to (new functionality)
-
Nevertheless efficient and performant
The Object-oriented paradigm aims to achieve all these goals
Object Dot Notation
How can you store the following data in an app?
user1: - name: ‘Tim’ - score: 3
user2: - name: ‘Stephanie’ - score: 5
In an object!
Objects store functions with their associated data.
This is the principle of encapsulation - and it is going to transform how we can ‘reason about’ code
Factory Functions
What is a built-in function of JavaScript who’s output will be an empty object?
Object.create
which will provide fine-grained control over the object later on.
A potential solution to avoid repeating yourself when creating multiple users would be as follows:
The instructor says not to use the above code in practice. The usable version is to follow.
Factory Functions Example
Instructor walks through the above example and mentions that they all share the increment function and if you wanted to add another key value pair to all of the user objects (imagine you have more than 2, but hundreds or more), this would not be practical.
Prototype Chain
The ‘better’ way to solve the problem would be to use the Prototype Chain.
Store the increment function in just one object and have the interpreter, if it does not find the function on user1
, look up to that object to check if it is there.
Link user1
and functionStore
so the interpreter, on not finding .increment
, makes sure to check up in functionStore
where it would find it
Make the link with Object.create()
technique.
Expanding on what we’ve seen above, consider the following:
MDN Object.create() documentation
Prototype Chain Example: Prototypal Link
This section walks through the above code.
Object.create(passedObject)
will create an implicit link to the passed-in object which will allow the new object to have access to properties that are in the passed-in object without directly having the properties itself.
Prototype Chain Example: Implicit Parameters
this
is an implicit parameter. In the case of the code above, in the increment function; this.score++
, this
will be equal to the value that is to the left of the dot, i.e. user1.increment()
will result in this = user1
in the execution context of the increment
function.
hasOwnProperty Method
What if we want to confirm if a property exists in an object? hasOwnProperty
! i.e. user1.hasOwnProperty('score')
. But does the user1 object have a method hasOwnProperty
? or does the object that it is prototypically linked to userFunctionStore
? no… JavaScript has a set of methods that are available to all objects set in Object.prototype
. In other words, every object in JavaScript has a prototypal link to a set of methods which are defined in Object.prototype
. user1.hasOwnProperty('score')
would return true
.
this Keyword
this
would work fine in the above code block, but consider this:
this
would not evaluate to user1
, but would look in Global Memory for user1
which does not exist, but if you change add1()
to add1.call(this)
, this
would still hold the same value in the execution context of add1()
as it did in the function stored in the label increment
Arrow Function Scope & this
When using an Arrow Function, the value of this
is lexically scoped meaning that the value of this
would remain as it was in the parent scope of the function, which would be user1
.
Prototype Chain Review
A review of the last few above sections.
new Keyword
the new
keyword automates the ‘hard work’ of creating objects. When we call the function that returns an object with new
in front of it, it automates 2 things:
-
Create a new object
-
Return the new object
But now we need to adjust how we write the body of our object creator. How can we:
-
Refer to the auto-created object? The newly generated object will be referred to as
this
-
Know where to put single copies of functions?
Functions are both objects and functions 🧐
All functions have a default property prototype
on their object version (itself an object). Using a dot on the end of a function, you can get access to the object portion of a function.
new Keyword Example
Walks through this code:
The benefit to using the new
keyword is that it is faster to write and it is often used in practice in professional code.
The problem(s) to using the new
keyword is that 95% of developers have no idea how it works and therefore fail interviews. And… we should uppercase the first letter of the function so we know it requires new
to work!
class Keyword
In languages other than JavaScript, there is a way to construct the function/object combo (similar to new
), but all at once rather than declaring a function, then assigning functions to labels in the .prototype
object. Using class
allows everything to be declared at once.
The benefits of using class
are that it is emerging as the new standard (introduced in ES6) and that it ‘feels’ more like the style of other languages (e.g. Python).
Problems with using class
? 99% of developers have no idea how it works…
Wrapping Up
Click Return to top
and re-read these notes.