Javascript: The Hard Parts, v2 Navigation

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:

const num = 3; // define a variable and assign it the number 3

function multiplyBy2 (inputNumber) { // defining the function
    const result = inputNumber*2; 
    return result;
}

const output = multiplyBy2(num); // declare a constant and define it to a function call
const newOutput = multiplyBy2(10); // declare a constant and define it to a function call

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:

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

Functions & Callbacks

Generalized Functions

function tenSquared() {
    return 10*10
}

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?

function squareNum(num) {
    return num*num
}

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

Higher order functions follow the same principle

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:

function copyArrayAndManipulate(array, instructions) {
    const output = [];
    for (let i = 0; i < array.length; i++) {
        output.push(instructions(array[i]));
    }
    return output;
}

function multiplyBy2(input) { return input * 2; }
const result = copyArrayAndManipulate([1, 2, 3], multiplyBy2);

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:

  1. Assigned to variables and properties of other objects

  2. Passed as arguments into functions

  3. 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

Arrow Functions

Arrow functions are a shorthand way to save functions

function multiplyBy2(input) { return input * 2; }

//  in an arrow function, becomes

const multiplyBy2 = input => input*2

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

Pair Programming

Pair programming is the most effective way to grow as an engineer, because:

Closure

Closure Introduction

Every time a function gets invoked, it creates a new store of memory (in its execution context)…but

Functions with memories!

Returning Functions

Returning a function from another function

function createFunction() {
    function multiplyBy2 (num) {
        return num*2;
    }
    return multiplyBy2;
}

const generatedFunc = createFunction();
const result = generatedFunc(3); // 6

Nested Function Scope

Calling a function in the same function call as it was defined:

function outer() {
    let counter = 0;
    function incrementCounter () {
        counter ++;
    }
    return incrementCounter;
}

outer();

const myNewFunction = outer();
myNewFunction();
myNewFunction();

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

Closure Exercises

Closure exercises are here

Asynchronous JavaScript

Single Threaded Execution Review

Promises, Async, and the Event Loop

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:

So what if we have a task:

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

// Slow function blocks further code from running

const tweets = getTweets("http://twitter.com/potus")

// 350ms wait while the request is sent to Twitter servers

displayTweets(tweets)

// more code to run

console.log('I am waiting for getTweets...')

JavaScript is not enough - we need new pieces (some of which are not JavaScript at all)

Our core JavaScript engine has 3 main parts:

We need to add some new components:

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:

function printHello(){console.log("Hello"); }
function blockFor1Sec(){
    // blocks the JavaScript Thread of Execution for 1 second, maybe with a for loop?
}

setTimeout(printHello,0);

blockFor1Sec();
console.log("Me first!");

What will happen?!

Callback Queue & Event Loop

Walking through the above JavaScript, this is what happens:

  1. Define function printHello
  2. Define function blockFor1Sec
  3. Run setTimeout Browser Feature, passing in the printHello function with 0ms passed in as the time argument. This adds the printHello function into the Callback Queue
  4. Call blockFor1Sec function
  5. The console.log prints
  6. 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:

Benefits:

Exercises

Promises

Promises Introduction

ES6+ Solution (Promises)

Using two-pronged ‘facade’ functions that both:

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

function display(data){
    console.log(data)
}

const futureData = fetch('https://twitter.com/username/tweets/1')
futureData.then(display);

console.log('Me first!');

Promises Example: fetch

In the example above, the following is happening:

  1. Declare a function display

  2. Declare a constant futureData and store the result of fetch(...). This will immediately return a ‘Promise Object’ with two properties; {value: undefined, onFulfilled: []} and store it in the futureData constant. The other consequence of fetch(...) 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 a GET 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 the futureData object that was created, specifically in the value property of that object.

Promises Example: then

  1. The onFulfilled array in the futureData object contains a number of [hidden] methods (which are ‘directly’ inaccessible), but can be accessed with ‘key’ words, in this case then. passing the display function into futureData.then() will automatically pass the content of futureData.value into the display function defined on the first line.

  2. The console.log('Me first!') will finally run and print Me first! in the console.

  3. The Network Request has successfully completed and the response is stored in futureData.value. This will trigger the ...then(display) and pass in futureData.value as the argument for the display function. A new execution context is created for the display function. futureData.value will be stored as data in the display function.

Web APIs & Promises Example: fetch

It is important to understand how promise-deferred functionality gets back into JavaScript to be run

function display(data){console.log(data)}
function printHello(){console.log("hello");}
function blockFor300ms(){// blocks js thread for 300ms}

setTimeout(printHello, 0);

const futureData = fetch('https://twitter.com/username/tweets/1')
futureData.then(display)

blockFor300ms()
console.log("Me first!");

The then method and its functionality to call on completion

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

List of Web APIs

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:

Benefits:

We have rules for the execution of asynchronously delayed code

Promises, Web APIs, the Callback & Microtask Queues, and Event Loop enable:

Classes & Prototypes

Class & OOP Introduction

Classes, Prototypes - Object Oriented JavaScript

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)

  1. Save data (e.g. in a quiz game the scores of user1 and user2)

  2. 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…

Code should be:

  1. Easy to reason about - Easy to figure out what is going on

  2. Easy to add features to (new functionality)

  3. 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

const user1 = {
    name: "First Name",
    score: 7,
    increment: function() { user.score++; }
};

user1.increment(); // user1.score -> 8

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:

function userCreator(name, score) {
    const newUser = {};
    newUser.name = name;
    newUser.score = score;
    newUser.increment = function() {
        newUser.score++;
    };
    return newUser;
}

const user1 = userCreator("Viljar", 3);
const user2 = userCreator("Yep", 5);
user1.increment();

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:

function userCreator(name, score) {
    const newUser = Object.create(userFunctionStore); // the argument passed into Object.create creates the 'link' to the object that is passed in as an argument
    newUser.name = name;
    newUser.score = score;
    return newUser;
}

const userFunctionStore = {
    increment: function(){this.score++;},
    login: function(){console.log("logged in");}
};

const user1 = userCreator("Viljar", 3);
const user2 = userCreator("Yep", 5);
user1.increment();

MDN Object.create() documentation

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:

const userFunctionStore = {
    increment: function() {
        function add1(){ this.score++; }
        add1();
    }
};

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 newkeyword 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:

  1. Create a new object

  2. Return the new object

But now we need to adjust how we write the body of our object creator. How can we:

function userCreator(name, score) {
    this.name = name;
    this.score = score;
};

userCreator.prototype
userCreator.prototype.increment = function(){
    this.score++;
}

const user1 = new userCreator("Viljar", 7);

Functions are both objects and functions 🧐

function multiplyBy2(num) {
    return num*2
}

multiplyBy2.stored = 5
multiplyBy2(3) // 6

multiplyBy2.stored // 5
multiplyBy2.prototype // {}

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:

function userCreator(name, score) {
    this.name = name;
    this.score = score;
};

userCreator.prototype
userCreator.prototype.increment = function(){
    this.score++;
}

const user1 = new userCreator("Viljar", 7);

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.

class UserCreator {
    constructor (name, score){ // same as function userCreator(name, score){...}
        this.name = name;
        this.score = score;
    }
    increment (){ this.score++; } // same as userCreator.prototype.increment...
    login (){ console.log("logged in"); } // same as userCreator.prototype.login...
}

const user1 = new UserCreator("Viljar", 7);
user1.increment();

constructor on MDN

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.