A walk in JavaScript

DAY 7

this Keyword

Over and over again I see engineers struggling with this topic; is so weird!! Long ago I found myself in the same situation, like, being writing code for many years and still … never took the time to really understand this when this is one of the most important and powerful features in JavaScript! Engineers we feel so frustrated about this that there’s even a joke for this!

JavaScript makes me want to flip the table and say “F* this shit”, but I can never be sure what **this refers to.

The good things came when I took the responsibility of this and accepted the guilt was entirely mine.

Why this intro? because there are tons of articles regarding this but everything about this was written by Kyle Simpson who dedicated a whole book for this topic , so we’re gonna read and study it until we breath this.

Resolving this

Let’s take a look at the following chapters of You Don’t Know JS: this & Object Prototypes - 1st Edition

Now let’s see how ECMAScript specifies the mechanism to resolve this.

In the other hand, MDN describes this on the Operators section

Explicitly binding this through prototype methods

Now we’ve learned that this has specific rules and it’s resolved at run-time, and we saw that the function prototype has 3 methods to explicitly define where to point when this needs to be resolved during it’s execution.

Now, there’s a catch!!! it seems that depending on a thing called mode, that depending on it’s strictness or non-strictness (a.k.a. Sloppy) it’ll alter the semantics and behavior of many things including this.


Strict Mode

What happens on strict mode?

TL;DR

  1. Eliminates some JavaScript silent errors by changing them to throw errors.
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that’s not strict mode.
  3. Prohibits some syntax likely to be defined in future versions of ECMAScript.

Semantic differences

When adding 'use strict'; the following cases will throw an Error:


Arrow Functions

An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors.

Source: MDN - Arrow Functions

Syntax

(param1, param2, , paramN) => { statements }
(param1, param2, , paramN) => expression
// equivalent to: => { return expression; }

// Parentheses are optional when there's only one parameter name:
(singleParam) => { statements }
singleParam => { statements }

// The parameter list for a function with no parameters should be written with a pair of parentheses.
() => { statements }

// Parenthesize the body of a function to return an object literal expression:
params => ({foo: bar})

// Rest parameters and default parameters are supported
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, , paramN = defaultValueN) => {
statements }

// Destructuring within the parameter list is also supported
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6

One of the most expected and misused features of ES6 is the Arrow Function. Undoubtedly powerful it might also derive in a headache if you don’t really know how they work and which are the differences between the full body notation and the arrow notation.

Let’s take a look at YDKJS - ES6 & Beyond - chapter 2


Generators

So far we’ve seen (except for the iterators) only run-to-completion examples of code. It means, “the execution won’t stop until it’s done or fails”. What if I tell you there’s a feature that let you define a function capable of being paused midway and resumed later?

Together with iterators ES6 introduced something called generators.

The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.

There are 2 ways to create a generator object

Note that GeneratorFunction is not a global object.

generator function objects created with the GeneratorFunction constructor are parsed when the function is created. This is less efficient than declaring a generator function with a function* expression and calling it within your code, because such functions are parsed with the rest of the code.

Source: MDN GeneratorFunction

Since this is a particularly complex topic, with several nuances, let’s try to understand them through examples:

Example of execution sequence

/**
 *
 * @param {number} initialValue
 * @returns {Object} Generator
 */
function* bottlesOfBeer (initialValue) {
  let bob = initialValue;
  let lastMessage = `No more bottles of beer on the wall, no more bottles of beer. 
Go to the store and buy some more, ${bob} bottles of beer on the wall.`;

  while (true) {
    console.log(`${bob} bottles of beer on the wall, ${bob} bottles of beer.`);

    yield bob--;

    console.log(`Take one down and pass it around, ${bob} bottles of beer on the wall.`);

    if (bob < 1) {
        bob = initialValue;
        console.log(lastMessage);
    }
  }
}

let bob = bottlesOfBeer(100);

bob.next();
// log -> 5 bottles of beer on the wall, 5 bottles of beer.
// statement completion value -> {value: 5, done: false}

bob.next();
// log -> Take one down and pass it around, 4 bottles of beer on the wall.
//        4 bottles of beer on the wall, 4 bottles of beer.
// statement completion value -> {value: 4, done: false}

// ... 3 more calls

bob.next();
// log -> Take one down and pass it around, 0 bottles of beer on the wall.
//        No more bottles of beer on the wall, no more bottles of beer. 
//        Go to the store and buy some more, 5 bottles of beer on the wall.
//        5 bottles of beer on the wall, 5 bottles of beer.
// statement completion value -> {value: 5, done: false}

// guess what happens now?

Passing values through next

/**
 *
 * @returns {Object} Generator
 */
function* passingValToNext () {
  let val = 10;

  while (true) {
    console.log(`UP val=${val}`);

    val = yield val + 10;

    console.log(`DOWN val=${val}`);
  }
}

let pvtn = passingValToNext();
// statement completion value -> passingValToNext {<suspended>}

pvtn.next(2);
// log ->  UP val=10
// statement completion value -> {value: 20, done: false}

pvtn.next(7);
// log ->  DOWN val=7
// log ->  UP val=7
// statement completion value -> {value: 17, done: false}

// WAIT! WHAT??!!!!
// how does it work?

Sample combining initial value and passing value to next

/**
 *
 * @param {Number} expectedTotal
 * @returns {Object} Generator
 */
function* calculateDownloadProgress (expectedTotal) {
  let totalDownloaded = 0;
  let newItems = 0;

  while (true) {
    totalDownloaded += newItems || 0; // lazy verification for the value passed by `next`

    let percent = ((totalDownloaded / expectedTotal) * 100).toFixed(2);

    newItems = yield `${percent}%`;
  }
}

let progress = calculateDownloadProgress(1024);
// statement completion value -> undefined
progress.next()
// statement completion value -> {value: "0.00%", done: false}
progress.next(15)
// statement completion value -> {value: "1.46%", done: false}
progress.next(500)
// statement completion value -> {value: "50.29%", done: false}

DIY

/**
 *
 * @returns {Object} Generator
 */
function* spinGen() {
  while(true){
    yield* ['\\', '|', '/', '--'];
  }
}

// now you add the code to see the output

Let’s take some time to read and discuss:


Exercises

Let’s open our test files:

Now open your terminal.

  1. Make sure you’re at the project location
  2. If you didn’t install all the packages yet then run npm i for a fresh dependency install, or npm ci for an installation based on the lock file.
  3. Type npm run test:watch, this will start running your tests every time you make a change.

Our task is to make ALL our DAY 7 tests pass ;)