Graduate Program KB

Deep Javascript


Table of Contents

  1. Types
    1. Primitive Types
    2. Typeof
  2. Special Values
    1. NaN
    2. Negative Zero
  3. Fundamental Objects
  4. Abstract Operations
    1. toString()
    2. toNumber()
    3. toBoolean()
  5. Coercion
    1. Boxing
  6. Equality
  7. Double Equals (==)
  8. Static Typing
    1. Typescript and Flow
    2. Pros and Cons of Static Typing
  9. Scope
    1. Executing Code
    2. Strict Mode
    3. Undefined vs Undeclared
    4. Function Expressions
    5. Named Function Expressions
    6. Arrow Functions
  10. Scope Continued
    1. Lexical Scope
    2. Function Scoping
    3. Block Scoping
    4. Choosing let or var
    5. const
  11. Hoisting
  12. Closure
    1. What is Closure?
    2. Modules
    3. ES6 Module Pattern/Syntax
  13. Object Oriented Systems
    1. This Keyword
    2. Arrow Functions and Lexical this
  14. ES6 Class Keyword
  15. Prototypes
    1. Prototypal Class
    2. Let's Simplify to OLOO

Types

Primitive Types

We know some primitive types exist in Javascript:

  • Undefined
  • Null
  • Boolean
  • String
  • Symbol
  • Number
  • Object

Typeof

typeof will always return a string. The string itself describes the typeof a variable. Therefore it can be said that typeof will always return one of a set of strings ("undefined", "string", "object" etc.).

Special values

NaN

Not-a-number (invalid number). Both programmers and mathematicians would agree that 0 is not a valid way to describe an absence of numerical value. NaN was created to show that there is no valid number available.

var myCatsAge = Number('n/a') // NaN
myCatsAge === myCatsAge // False
Number.isNaN(myCatsAge) //True

If you do not own a cat, saying it has an age if 0 does not make numerical sense. So we use "n/a" instead to show NaN. NaN also takes precedence, performing addition of two values (with one being a NaN) will result in NaN. However, as shown on the second line, myCatsAge is not equal to itself, this is a property of NaN. Instead we could utilise the third line to check if a variable is NaN or not.

Negative Zero

Negative zero exists in javascript. We can use Object.is([varName], -0) to check if a variable is equal to -0. Converting to string will result in "0", rather than "-0" as we would want. We may want to use -0 for showcasing a trend-rate. With negative numbers signifying a downturn, and positive an up. -0 would need to be used in certain cases in this example to demonstrate a downturn, even with no change.

Fundamental Objects

Also known as built-in object or native functions. We should use the new keyword when constructing an object of that fundamental type. It should be used for:

  • Object()
  • Array()
  • Function()
  • Date()
  • RegExp()
  • Error()

It should not be used for:

  • String()
  • Number()
  • Boolean()

Abstract Operations

These operations are not a part of the ECMAScript language; they are defined to aid th specification of the semantics of the language. toPrimitive takes in a hint as an argument. Performing on a string, the hint would be string (which specifies what we would like the return value to be, this is not guaranteed). Using toPrimitive on a number, will first call valueOf() to check what type the item is, then (for example) call toString() to attempt to convert the value to string form.

toString()

To string can be used to operate on a value, and it attempts to convert the value to its string representation. As we learnt before calling toString on "-0" will return "0". Some examples of what will happen when we call toString on an array are below:

- [] = ""
- [1,2,3] = "1,2,3"
- [null,undefined] = ","
- [[[],[],[]]] = ",,,"
- [,,,,] = ",,,"

Performing toString on objects will also give us some odd values:

- {} = "[object Object]"
- {a : 2} = "[object Object]"
- {toString(){ return "X";}} = "X"

toNumber()

Some classic example we get when calling toNumber are shown below:

- "" = 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

Performing toNumber on an object is peculiar, and more often than not the output will reflect this:

- [""] = 0
- ["0"] = 0
- ["-0"] = -0
- [null] = 0
- [undefined] = 0
- [1,2,3] = NaN
- [[[[]]]] = 0

toBoolean()

toBoolean determines if a value is Falsy, or Truthy. It essentially has a lookup table, and determines which category it should fall into. There is a short list of falsy values:

  • ""
  • 0, -0
  • null
  • NaN
  • false
  • undefined

Everything that is not on that list, is considered a truthy value, and toBoolean will return 'true' for them.

Coercion

We all use coercion whether or not we realise it. For example, parsing in non-string's to template literals. E.g.:

var numStudents = 16; // This is a number
console.log(
    `There are ${numStudents} students.`
): // Will output: "There are 16 students"

Here we are actually parsing a number in, yet we get a complete string returned. To explicity coerce a value, we could use 'String()'.

var numStudents = 16; // This is a number
console.log(
    `There are ${String(numStudents)} students.`
): // Will output: "There are 16 students"

You want to avoid using coercion on booleans. Additionally, non-booleans should be avoided in an if-statement where possible. This is because we may get bugs where we don't know if a value will be truthy or falsy. We can happily use Boolean coercion when checking if something has been 'set' yet. We know we that undefined and null are equal to falsy values, so we can use this to check an object if it has been set, with false indicating no, and true indicating yes.

Boxing

This is a form of implicit coercion. If you have a thing that is not an object, but are trying to use it like an object, JavaScript will 'coerce' our thing into an object so that we can use it like an object. That is why we can do this:

if (studentNameElem.value.length > 3) {
  // Do something
}

Equality

Both == and === check the types of the values to be checked. When the types (and values) match, both == and === are equitable. They will provide the exact same output, and either can be used. For triple equals, the first thing it does is check the types, and if these are not equal, then it will return false, regardless of what the values are.

var workshop1 = {
  name: 'Deep JS Foundations',
}

var workshop2 = {
  name: 'Deep JS Foundations',
}

if (workshop == workshop2) {
  // Will not be equal
}
if (workshop === workshop2) {
  // Will not be equal
}

Although they have the same value for name, and are of the same type, because they are different objects, both operators will return negative. Essentially, == allows coercion when the types are different, and === disallows coercion when the types are the same.

Double Equals (==)

Suppose we have variables x and y. If value x is null, and y is undefined, == will return true and the same if the variables values are switched. Double equals will always try to do a numeric comparison whenever possible. This means that if one of the two variables is a number, it will attempt to convert the secondary variable (if not a number) to a number to be able to evaluate the comparison. For example:

var myNumber = 12
var myString = '12'
if (myNumber == myString) {
  // myString will be converted to a number for the evaluation as myNumber is a number)
}

The double equals operator only evaluates on primitives. If one of them is not primitive it would have to coerce the variable into a primitive (using ToPrimitive(variable)).

An odd example of using double equals operator:

var workshop1Count = 42
var workshop2Count = [42]

if (workshop1Count == workshop2Count) {
  // Returns true, but should it?
}
// Here's how it happens:
// if(workshop1Count == workshop2Count)
// if(42 == "42")
// if(42 === 42)
// Returns true

Summary of "==" If the types are the same, then triple equals will be used. If null or undefined, they will be equal to each other. If non-primitives are compared, ToPrimitive will be called. And the double equal operator will prefer to perform a numeric comparison (will convert ToNumber if at least one variable is a number).

When using ==, avoid these things:

  1. == with 0, "" or " "
  2. == with non-primitives
  3. == true or == false. Instead use === or allow for ToBoolean to occur

We should use == wherever possible rather than ===. It will force us to know what types we are passing to the operator. Double equals should only be used when we know the types, and therefore we must strive to make our code more clear, by ensuring certain types only during our equality.

Essentially we can follow this rule: "Making types known and obvious leads to better code. If types are known, == is best. Otherwise we fall back to ===".

Static Typing

Typescript and Flow

Benefits:

  • Catch type-related mistakes
  • Communicate type intent
  • Provide IDE feedback

Caveats:

  • Inferencing is best-guess, not a guarantee
  • Annotations are optional
  • Any part of the application that isn't typed introduces uncertainty

Example in TS/Flow:

var teacher = "Kyle";
teacher = { name: "Kyle" };
// Error: Cannot assign object to a string

Teacher can also be annotated as a string:

var teacher: string = "Kyle";
// We will still get the same error as before

In Typescript, you can also make your own customer types and signatures.

type student = { name: string }; // Define our own type student, which has a name of type string
function getName(studentRec: student): string { // Passing in studentRec of type student
  return studentRec.name
}
var firstStudent: student = { name: "Frank" };
var firstStudentName: string = getName(firstStudent);

Pros and Cons of Static Typing

Pros:

  • They make types much more obvious when writing code
  • Familiarity: They are very similar to other language's type systems
  • Extremely popular (easier to access useful resources)
  • They are sophisticated and good at what they do

Cons:

  • They use 'non-JS-standard' syntax
  • They require a build process, which raises the barrier to entry
  • Their sophistication can be intimidating to those without prior formal types experience
  • They focus more on 'static types' (variables, parameters, returns, properties etc.) rather than value types

Scope

Scope is where we look for things. We look for identifiers. All variables either receive an assignment of some value, or we are retrieving a value from that variable. When evaluating a variable, JS determines what state the variable is in, and what scope it belongs to.

Javascript organises scopes with functions and blocks. As JS runs through code execution, it can recognise variables which have global scope or, a different kind of scope (perhaps within a function) and it remembers these different scopes for later execution. Kyle uses an example of buckets and marbles, with the buckets representing the scope, and the marbles representing something within a scope. So buckets can be within other buckets, and each bucket will be filled with different marbles (different variables within scope).

Executing Code

var teacher = 'Kyle' // Target position (assigning a value to a variable)

function otherClass() {
  var teacher = 'Suzy' // Target position
  console.log('Welcome') // Global scope will be checked as 'console' cannot be found within the scope (we did not declare it)
}

function ask() {
  var question = 'Why?' // Target position
  console.log(question) // Source position (finding the value of variable)
}

otherClass() // Output: Welcome
ask() // Output: Why?

Both calls to the functions are considered to be a source references within the global scope. Within each function, they have their own scope with variables which are locally scoped to the function themselves.

An issue to be aware of is when we reference a variable within a function, which does not have a declaration anywhere. See below:

var teacher = 'Kyle'

function otherClass() {
  teacher = 'Suzy'
  topic = 'React'
  console.log('Welcome!')
}
teacher // Output: "Kyle";

otherClass()
teacher // Output: "Suzy";
topic // Output: "React";

Here we have a variable teacher within the global scope which has the value of 'Kyle'. Within the scope of our function otherClass(), we have a reference to a variable called teacher. Javascript will look back one level of scope (in this case to global scope), and see if there are any declarations for that variable. It finds one, and sets the variable teacher to be 'Suzy'. The next line, we see a variable topic, and looking at the global scope, we notice there is no declaration. Here Javascript will automatically create a global variable for you, and assign a value to it. This kind of behaviour is dangerous. This occurs when programs do not run in strict mode.

Strict Mode

We can place a program into script mode by placing "use strict"; at the top of our file. In our previous example, topic will return a ReferenceError. Javascript by default does not use strict-mode.

undefined vs. undeclared

Undefined means a variable exists but at the moment it has no value. Undeclared is never formally declared in any scope that we have access to.

Function Expressions

function teacher() {
  /* ...*/
} // This is a function declaration

var myTeacher = function anotherTeacher() {
  // This is a function expression
  console.log(anotherTeacher) // This works fine
}
console.log(anotherTeacher) // This will result in a reference error

Here the teacher function has global scope, but surprisingly the anotherTeacher function does not, it has a nested level of scope. Function declarations add their name to their enclosing scope, so we can reference them with that name. Function expressions add their name to their own scope, hence why we can call the function from within itself with console.log(anotherTeacher);, but when we call call it from outside the function expression scope, we receive a reference error.

Named Function Expressions

This is a function expression which has been given a name (see above to see the difference between function expression and function statement).

var clickHandler = function () {
  // Anonymous function expression
  // ...
}
var keyHandler = function keyHandler() {
  // Named function expressions
  // ...
}

Why we should prefer a named function expression rather than anonymous function expression:

  1. Reliable function self-reference (recursion etc.)
  2. Stack traces are easier to debug (you can see the function names)
  3. More self-documenting code (describes what your function is doing)

Overall however, we should typically prefer a function declaration when possible.

Arrow Functions

var ids = people.map((person) => person.id)

var ids = people.map(function getId(person) {
  return person.id
})

Arrow functions are anonymous functions, which typically makes it harder to understand what the functions purpose is. If it were named, then this process would become far easier. They do however keep code far more concise due to their nature. Promise chains typically use arrow functions. Arrow functions will be discussed again in more depth later.

Kyle believes the function types hierarchy follows the below list in order from best to worst:

  1. (Named) Function Declaration
  2. Named Function Expression
  3. Anonymous Function Expression

Scope Continued

Lexical Scope

At item's lexical scope is the place in which it was created. The scope of a variable is determined by the program's textual (lexical) structure. A variable declared/initialised within a function, has the lexical scope of that function.

Function Scoping

Say we have the below code:

var teacher = 'Kyle'
console.log(teacher) // Kyle

Simple enough. But then we have another developer who comes along and reassigns teacher to a different value without realising that it had already been declared with a value. This would obviously affect the output of our console.log statement. But we could wrap the re-assignment of teacher within a function, keeping the new value within the function scope only. See below:

var teacher = 'Kyle'

function anotherTeacher() {
  var teacher = 'Suzy'
  console.log(teacher) // Suzy
}
anotherTeacher()
console.log(teacher) // Kyle

But now we have the issue that anotherTeacher is a public function, which could be access/changed/mutated by other people, which once again could affect our output of console.log (evidently this is a basic example but the principle remains the same for all complexity types).

Instead what we could do is wrap our function with a set of parenthesis, to turn it into an IIFE (immediately-invoked-function-expression).

//  prettier-ignore
var teacher = "Kyle"
// IIFE
( function anotherTeacher(){
    var teacher = "Suzy";
    console.log(teacher); // Suzy
}) (); // Functioned wrapped in parenthesis. Second set used to invoke the function immediately
console.log(teacher) // Kyle

This is not a function declaration because the word function, is not the first thing in the statement. This essentially turns it into an expression (syntactically), rather than a declaration.

Block Scoping

Instead of writing an IIFE, we could write it as a block. Taking the previous example, it can be re-written like so:

var teacher = 'Kyle'
{
  let teacher = 'Suzy' // Notice we use let here instead of var, this is due to var accessing global variables
  console.log(teacher) // Suzy
}
console.log(teacher) // Kyle

This method is far less complex for the simple task that was required of the example. There will be use cases for both ways of writing with either IIFE or blocks based on the context of the situation.

Choosing let or var

Inside a function, if we have a variable at the top level of the function, it should be initialised with var rather than let. Then let can be used from within the function for items like a for-loop or within further scopes of the function, where you want the variable to be local to only that nested scope. Example below:

function repeat(fn, n) {
  var result // Using var as we want this in the whole function (let would still work)
  for (let i = 0; i < n; i++) {
    // Here we use let, as it is only scoped within the for-loop
    result = fn(result, i)
  }
  return result
}

Most developers are of the opinion that we should use let wherever we can instead of var, Kyle does not follow this methodology.

const

In Javascript, const is a keyword which can be used to declare a variable that cannot be reassigned a new value.

const teacher = 'Kyle'
teacher = 'Matt' // TypeError
// Consider:
const teachers = ['Kyle', 'Matt']
teachers[1] = 'Brian' // This is allowed

Evidently, this could cause confusion as we cannot change the value of a variable, yet we can mutate an array declared with const.

Hoisting

Hoisting does not really actually exists, it is just a way to better describe the way javascript operates on variables between multiple scopes. Functions are 'hoisted' to the top. An example of 'hoisting':

function teacher() {
  return 'Kyle'
}
var otherTeacher

teacher() // Kyle
otherTeacher() // TypeError

otherTeacher = function () {
  return 'Suzy'
}

The statement 'let doesn't hoist' is false. Let does hoist, but it operates different to var in that when let is hoisted, it creates a location called [variable] but it is uninitialised, whereas var is initialised and labelled undefined.

var teacher = 'Kyle'
{
  console.log(teacher) // TDZ Error
  let teacher = 'Suzy'
}

In this example, if let did not hoist, then console.log would have printed "Kyle". But instead we got an error, because let did hoist, but it is not yet initialised.

Closure

What is Closure?

Closure is when a function 'remembers' its lexical scope even when the function is executed outside that lexical scope. The preservation/link back to the original scope, no matter where you pass it.

function ask(question) {
  setTimeout(function waitASec() {
    console.log(question)
  }, 100)
}
ask('What is closure?')
// What is closure?

By the time the waitASec function executes, the ask function is already finished, and the variable question which it is closed over should have 'gone away'. But it didn't because the waitASec() function is closed over the variable 'question'. That is a clear example of closure.

We do not close over a value, we close over a variable (which can have a value for that moment in time). So we may have a variable which has a value, but by the time one of our functions executes, the variable has a different value. We close over the variable, so whatever value the variable has at the time will be used.

for (var i = 1; i <= 3; i++) {
  setTimeout(function () {
    console.log(i)
  }, i * 1000)
}
// 4
// 4
// 4

This example runs the function 3 times (as the loop executes), however due to the setTimeout() function, by the time the console runs, the value of the variable i is equal to 4. Hence 4 prints for each time.

Modules

Placing a set of functions and data, and putting them as properties (instead of variables) inside an object, this is essentially 'Namespace'. This is not a module! The module pattern requires the concept of encapsulation. Encapsulation really just means the idea of collecting and hiding data (some control over visibility). The idea of a module is about the visibility (sometimes we have public or private functions/information).

Modules: "Encapsulate data and behaviour (methods) together. The state (data) of a module is held by its methods via closure". Here is an example:

var workshop = (function Module(teacher) {
  var publicAPI = { ask }
  return publicAPI
  // **********
  function ask(question) {
    console.log(teacher, question)
  }
})('Matt')
workshop.ask("It's a module, right?")
// Matt It's a module, right?

It is similar to an IIFE in that it has the braces which executes the function, however instead of running once them being done, it runs then is 'done' however the closure prevents the scope from going away. The second aspect is the ask() function which is closed over the variable teacher, the workshop object on the outside which has reference to that function, is preserving that inner-scope through closure. What is being done with the publicAPI variable, is allowing outside access to the ask() function. There could be hundreds of private methods inside the object which cannot be externally access, so we can select and choose what is public or private.

We can change the previous example even further and turn it into a Module Factory. This means we can call it 10 (as many as we want) times, with 100 different values, and we will have 100 different instances of that module.

function WorkshopModule(teacher) {
  var publicAPI = { ask }
  return publicAPI
  // **********
  function ask(question) {
    console.log(teacher, question)
  }
}
var workshop = WorkshopModule('Matt')

workshop.ask("It's a module, right?")
// Matt It's a module, right?

ES6 Module Pattern/Syntax

(May be updated due to old information). Writing ES6 modules, we place them in their own file with a .mjs suffix on the file name, rather than the usual .js. All the functions within the file are considered private (think of it as wrapped in a larger function). The way to make the functions public, we can use 'export default' before the function declaration/expression.

Our workshop.mjs file:

var teacher = 'Kyle'
export default function ask(question) {
  console.log(teacher, question)
}

A different .js file:

import ask from 'workshop.mjs' // Default import
ask("It's a default import, right?")
// Kyle It's a default import, right?

import * as workshop from 'workshop.mjs' // Namespace import
workshop.ask("It's a namespace import, right?")
// Kyle It's a namespace import, right?

Object/s Oriented Systems

this keyword

A functions this references the execution context for that call, determined entirely by how the function was called. A this-aware function can thus have a different context each time it's called, which makes it more flexible and reusable.

function ask(question) {
  console.log(this.teacher, question)
}
function otherClass() {
  var myContext = {
    teacher: 'Suzy',
  }
  ask.call(myContext, 'Why') // Suzy Why?
}
otherClass()

In Javascript there are 4 different way to invoke a function. The first is implicit binding.

var workshop = {
  teacher: 'kyle',
  ask(question) {
    console.log(this.teacher, question)
  },
}
workshop.ask('What is implicit binding?')
// kyle What is implicit binding?

If we had a second workshop instance with a different value for teacher, then calling the ask() function on the second workshop would call the ask() function with the new value for the teacher variable. The next way to invoke a function is with explicit binding:

function ask(question){
  console.log(this.teacher, question)
};

var workshop1{
  teacher = "Kyle",
  ask: ask
}
var workshop2 {
  teacher = "Suzy",
  ask: ask
}
ask.call(workshop1, "Can I explicitly set context?");
// Kyle Can i explicity set context?
ask.call(workshop2, "Can I explicitly set context?");
// Suzy Can i explicity set context?

This differs to the last example that instead of using 'workshop.call', we instead do 'ask.call' to invoke the function. A variation of explicit binding to be aware of is hard binding:

var workshop = {
  teacher: 'Kyle',
  ask(question) {
    console.log(this.teacher, question)
  },
}
setTimeout(workshop.ask, 10, 'Lost this?')
// undefined Lost this?
setTimeout(workshop.ask.bind(workshop), 10, 'Hard bound this?')
// Kyle Hard bound this?

The third way to invoke a function is with the new keyword.

function ask(question) {
  console.log(this.teacher, question)
}
var newEmptyObject = new ask("what is 'new' doing here?")
// undefined What is 'new' doing here?

The purpose of the new keyword is to invoke a function with the keyword pointing at a whole new empty object. The new keyword does 4 very specific things here (which aren't obvious):

  1. Create a brand new empty object
  2. Link that object to another object
  3. Calls the function with this set to the new object
  4. If the function does not return an object, it assumes the return of this

Our fourth and final way to invoke a function is to use default binding:

var teacher = 'Kyle'

function ask(question) {
  console.log(this.teacher, question)
}

function askAgain(question) {
  'use strict'
  console.log(this.teacher, question)
}
ask("What's the non-strict-mode default?")
// Kyle What's the non-strict-mode default?
askAgain("What's the strict-mode default?")
// TypeError

Arrow Functions and Lexical this

If you put a this within an arrow function, it will act like an other variable in that it will lexically go up in scope until it finds a defined this.

var workshop = {
  teacher: 'Kyle',
  ask(question) {
    setTimeout(() => {
      console, log(this.teacher, question)
    }, 100)
  },
}
workshop.ask("Is this lexical 'this'?")
// Kyle Is this lexical 'this'?

The specifications say: "An ArrowFunction does not define local bindings for arguments, super, this, or new.Target. Any reference to [...] within an ArrowFunction must resolve to a binding in a lexically enclosing argument." If the above example, this goes up one level of scope to the ask() function to find its value. If we removed the seTimeout, then this would go up one level of scope to global (just because the object has curly braces doesn't mean it is a scope), and it would not find a value there. Kyle is of the opinion that arrow functions should only ever be used when you need lexical this.

ES6 Class Keyword

Here is an example of a class within Javascript:

class Workshop {
  constructor(teacher) {
    // Optional constructor
    this.teacher = teacher
  }
  ask(question) {
    console.log(this.teacher, question)
  }
}
var deepJS = new Workshop('Kyle')
var reactJS = new Workshop('Suzy')
deepJS.ask("Is 'class' a class?")
// Kyle Is 'class' a class?
reactJS.ask('Is this class OK?')
// Suzy Is this class OK?

We can then further extend classes into another class for the sake of inheritance. Below uses our last class, and a new one:

class AnotherWorkshop extends Workshop {
  speakUp(msg) {
    this.ask(msg)
  }
}
var JSRecentParts = new AnotherWorkshop('Kyle')
JSRecentParts.speakUp('Are classes getting better?')
// Kyle Are classes getting better?

Prototypes

Objects are built by 'constructor calls' (via new). A 'constructor call' makes an object 'based on' its own prototype. Kyle refers to an example involving a child. At the instant a child is born, it has essentially copied it's parents, it has all of their DNA. But as they grow an evolve, they do not continually copy. If the parent were to get a tattoo, the child would not magically receive one. This is how classes and inheritance can be described. At the moment of creation, a child class may copy a parent, but from there it can evolve and change, and they will not both be identical. Now a better way to phrase our previous statements would be: "A 'constructor call' makes an object linked to its own prototype".

Javascript's prototype system is a delegation system, not a class system. It deals with behaviour delegation, when boiled down to it's simplicities.

Prototypal Class

Although we likely wouldn't write our code like this any more, and instead just use the class syntax, it is important to understand what is going on behind the scenes.

function Workshop(teacher) {
  this.teacher = teacher
}
Workshop.prototype.ask = function (question) {
  console.log(this.teacher, question)
}
var deepJS = 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?

Let's Simplify to OLOO

Kyle describes this as Objects Linked to Other Objects. Below is the OLOO style of the class system we had before which included a Workshop with AnotherWorkshop extending this class.

var Workshop = {
  setTeacher(teacher) {
    this.teacher = teacher
  },
  ask(question) {
    console.log(this.teacher, question)
  },
}
var AnotherWorkshop = Object.assign(Object.create(Workshop), {
  speakUp(msg) {
    this.ask(msg.toUpperCae())
  },
})
var JSRecentParts = Object.create(AnotherWorkshop)
JSRecentParts.setTeacher('Kyle')
JSRecentParts.speakUp("But isn't this cleaner?")
// Kyle BUT ISN'T THIS CLEANER?

In this iteration, all we have is objects, there are no classes. And when we call Object.create, we simply link an object to AnotherWorkshop, which itself is linked to Workshop. There is very little syntactic difference between this example and the class example, it more clearly displays that objects are simply linked, rather than the syntactic sugar that classes provides.

One way of utilising inheritance, in a scenario where we have two separate classes/objects needed to be used to complete a task can be done in the following way:

var AuthController = {
  authenticate() {
    server.authenticate(
      [this.username, this.password],
      this.handleResponse.bind(this),
    )
  },
  handleResponse(resp) {
    if (!resp.ok) this.displayError(resp.msg)
  },
}
var LoginFormController = Object.assign(Object.create(AuthController), {
  onSubmit() {
    this.username = this.$username.val()
    this.password = this.$password.val()
    this.authenticate()
  },
  displayError(msg) {
    alert(msg)
  },
})

This way makes it far more testable, in addition to keeping the this context.