Mon 21 May 2018 — Fri 15 Jun 2018

JavaScript Update

Notes on modern additions to JavaScript.

Use Strict

Adding this declaration makes JavaScript a bit less dodgy.

"use strict";

thing = 10; // No assigning to undeclared variables.

if (true) {
    // No function declarations inside blocks.
    // This is dodgy because it's confusing:
    // functions are hoisted anyway in JavaScript.
    function shouldBreak() {
        console.log("This shouldn't work in strict mode.");
    }
    shouldBreak();

    var shouldWork = function() {
        console.log("This should work in strict mode.");
    }
    shouldWork();
}

(function globalThis() {
    // Functions which are not methods and not bound
    // don't get the global object as their /this/ object in
    // strict mode.
    console.log("The /this/ object should be undefined", this);
})();

delete undeclared; // Deleting unqualified variables is disallowed.
delete this.undeclared; // Deleting qualified variables still allowed.

console.log(010); // Octal numbers disallowed.

// Variables declared inside eval aren't leaked.
eval("var leaked = 10");
console.log("leaked should be undefined", leaked);

with ({ x: 1}) { // With is disallowed.
    console.log("Shouldn't get here", x)
}

"hello".length = 10; // Setting immutable properties disallowed.

Let and Const

JavaScript variables are normally function-scoped.

The let keyword makes them block-scoped (so if-else, for and while constructs get a new scope). The const keyword does the same, and also prevents rebinding that variable.

Modules

A file is considered to be a module.

You can write export in front of a variable or named function to export it.

Another file can import specific values from that module using import statements:

import * from 'someModule';
import {A as a, B as b} from 'someModule';
// There are other options, but these cover most of it.

Numbers

ES6 (2016) added an exponentiation operator.

console.log(2 ** 8);
256

Strings

You can make a multiline string by ending a line with a \.

String literals can be written using backticks instead of quotes. These strings are multiline, and get expression interpolation:

console.log(
    `${2 ** 8}`
);
256

There are some new string padding methods. So hey, maybe something good came out of all the left-pad trouble:

console.log(
    "hello".padStart(10, "oi"),
    "goodbye".padEnd(13, "ai"),
);
oioiohello goodbyeaiaiai

Template Literals

These are for building DSLs. They let you define a string which gets passed to a function for processing in both raw and interpolated/escaped forms.

Regular Expressions

Regular expression literals are surrounded by forward slashes.

/[0-9]+/; // regex which matches numeric digits.

Sets and Maps

Set behaves like sets do in most other languages: values are deduplicated. Uniqueness is tested using ===, except for NaN, which is equal to itself (this behaviour differs from ===).

Map is a better dictionary than a normal object. You can use any values for the keys. Iteration order is guaranteed to be the same as insertion order.

It uses the same equality testing for keys as Set does.

There are WeakSet and WeakMap equivalents, for if you need to keep a reference to something, but still allow it to be garbage collected. Useful for implementing caches.

Arrays

Several useful functions were added in ES5, which let you do some basic functional programming.

  • Array.isArray()
  • Array.prototype.every()
  • Array.prototype.filter()
  • Array.prototype.forEach()
  • Array.prototype.indexOf()
  • Array.prototype.lastIndexOf()
  • Array.prototype.map()
  • Array.prototype.reduce()
  • Array.prototype.some()
  • checks if thing is one of the array's (or object's) values.

We can abbreviate Array.prototype.someMethod.call() as [].someMethod.call();x

These are useful, but quite limited. Consider using Lodash.

JSON

JSON.parse() and JSON.stringify() were both new in ES5.

Functions

We were always able to write anonymous functions:

console.log(
    [1, 2, 3].map(function(a) { return a * 2; })
);

[ 2, 4, 6 ]

Now we can also write them with a fat arrow syntax.

console.log(
    [1, 2, 3].map((a) => a * 2)
);
[ 2, 4, 6 ]

Arrow functions don't have this and arguments special parameters. If you use these, they'll come from the enclosing scope.

Functions can now also have default parameters:

(function(a = "hello") {
    console.log(a);
})();
hello

Parallelism

You can now allocate shared memory using SharedArrayBuffer. You make a clone of the object produced, and then give it to some other thread (usually a Worker).

const myCopy = new SharedArrayBuffer(sizeInBytes);
const yourCopy = {myCopy}; // Shouldn't really keep this constant around.
someWorker.postMessage(yourCopy);

The Atomics object has some functions to let you make synchronized updates to this shared memory.

Setting and getting:

  • Atomics.load(typedArray, index)
  • Atomics.store(typedArray, index, value)
  • returns the current value
  • like exchange, but checks if the current value is what we expected first.

Notifications:

Atomics.wait(typedArray, index, value, timeout)
wait at typedArray[index], as long as typedArray[index] === value now.
Atomics.wake(typedArray, index, value)
trigger waiting workers

Arithmetic and bitwise operations on (typedArray, index, value):

  • add
  • sub
  • and
  • or
  • xor

The typed arrays here must be backed by a SharedArrayBuffer. They must be aligned correctly, and the sizes must all be correct.

At the moment, the only type of array allowed is Int32Array.

Objects

By default in JavaScript, an object's prototype is decided by the prototype property of its constructor. (For object literals, this is Object.prototype.)

This is needlessly complicated, but they wanted it to look like Java.

Make sure not to return anything (other than possibly this) from your constructor function. If you do, it will become your prototype, and everything you did to this will be ignored.

"use strict";

const C = function() {
    this.urgh = "argh";
};

C.prototype.yaaaarp = function() {
    return "I've got a brand new combine harvester.";
};

const c = new C();

console.log("prototype on constructor:", C.prototype);
console.log("prototype on instance:", Object.getPrototypeOf(c));

console.log(
    "constructor property on prototype property on constructor:",
    C.prototype.constructor.name
);

console.log("constructor on instance:", c.constructor.name);

console.log("Property set in constructor function exists on instance:", c.urgh);

console.log(
    "Property set in constructor function missing from prototype:",
    Object.getPrototypeOf(c).urgh
);

console.log("Method on prototype available to instance:", c.yaaaarp());

console.log(
    "Don't return anything from the constructor:",
        new (
            function D() { return {}; }
        )().constructor.name
);
prototype on constructor: C { yaaaarp: [Function] }
prototype on instance: C { yaaaarp: [Function] }
constructor property on prototype property on constructor: C
constructor on instance: C
Property set in constructor function exists on instance: argh
Property set in constructor function missing from prototype: undefined
Method on prototype available to instance: I've got a brand new combine harvester.
Don't return anything from the constructor: Object

Accessing the prototype (use instead of __proto__):

Object.create(prototype, propertyDescriptors)
create an object with the given prototype. See property descriptors below.
Object.getPrototypeOf(thing);
Object.setPrototypeOf(thing, newPrototype);
this is a slow operation.

Classes

EcmaScript 2015 added class-based inheritance. This is just some syntactic sugar over prototypical inheritance.

In fact, classes are just functions and you can assign them to variables.

We have single-inheritance, static methods (also inherited).

However, you can make a function that takes a class and returns a new class extending it. You can therefore make a style of mixin which is actually just a really long generated inheritance chain.

class MyLovelyWhat {
    constructor(what) {
        this.what = what;
    }

    toString() {
        return `My lovely ${this.what}`;
    }

    static describe() {
        return "Static method on the MyLovelyWhat base class";
    }

    speak() {
    }
}

// Classes are actually just a special kind of function, so you can
// assign them to variables too.
const MyLovelyHorse = class extends MyLovelyWhat {
    constructor() {
        super("horse");
    }

    speak() {
        return "Neigh!";
    }
}

const lovely = new MyLovelyHorse();
console.log(`${lovely.toString()}, running through the fields.`);
console.log(`Horse says ${lovely.speak()}`);
console.log(`What says ${new MyLovelyWhat("Huh").speak()}`);

// Static methods *are* inherited.
console.log(`What class calls ${MyLovelyWhat.describe()}`);
console.log(`Horse class calls ${MyLovelyHorse.describe()}`);
My lovely horse, running through the fields.
Horse says Neigh!
What says undefined
What class calls Static method on the MyLovelyWhat base class
Horse class calls Static method on the MyLovelyWhat base class

Properties

There are three main types of properties:

data properties
these are the normal ones
accessors
with getters and setters
internal properties
can't touch this

To look at an object's properties:

Object.keys(thing)
returns s list of keys.
Object.values(thing)
returns a list of values.
Object.entries(thing)
returns a list of (key, value) pairs.
Object.getOwnPropertyDescriptors(thing);
gives you some helpful reflection information.

A property descriptor looks like this:

{
    value: "whatever",

    writable: true, // can you change the property's value?

    enumerable: true, // if this is false, hide the property from some operations

    // if this is false, you can't delete the property or change
    // anything about it except for its value
    configurable: true
}

We can modify some of these things:

Object.defineProperty(thing, name, propertyDescriptor)
add a property to an object. There's also a defineProperties(thing, propertyMap) batch variant.
Object.preventExtensions(thing);
no more properties on this object, but you can still delete them.
Object.seal(thing);
as above, but you can't delete or configure properties either. This is something like locking the object to a class.
Object.freeze(thing);
as above, but all properties are made read-only.

Accessors

These are a lot like C# properties, having a getter and an optional setter.

Some of the special new things in JavaScript are implemented using these.

var thing = { // Inherits from Object
    get bleh() {
        // Careful not to use the same name here — it's easy to set up
        // an infinite recursion by mistake.
        return this._bleh || "meh";
    },
    set bleh(bleh) {
        this._bleh = bleh;
    }
};

console.log(thing.bleh);
thing.bleh = "heh";
console.log(thing.bleh);
meh
heh

Accessors have slightly different property descriptors to data properties:

{
    get: function() { return "whatever"; },
    // missing set function means this property is immutable
    enumerable: true,
    configurable: true
}

Iteration

Iterable objects have a Symbol.iterator method. You need to use thing[Symbol.iterator] to get to this: its name interferes with the normal dot syntax.

Calling this method gets you an iterator. This has a next() method, which gives a you an object: {result: something, done: false}.

There's a new for-of syntax which loops over iterators, must like foreach loops in other languages.

You can make a function into a generator by putting an * after it and using the yield keyword.

Lastly, there's a special yield* someOtherGenerator() call which yields all the things that generator would yield.

function* myThing() {
    yield 1;
    yield 2;
    yield 3;
}

function* myOwningThing() {
    yield* myThing();
    yield 4;
    yield 5;
}

for (const x of myOwningThing()) {
    console.log(x);
}

1
2
3
4
5

Asynchrony

JavaScript has gotten a new Promise object, which tells you the state of a computation which may not have happened yet. This has some methods:

.then(result => {})
when computation finishes
.catch(error => {})
when computation fails
.finally(() => {})
resource cleanup after one of then and catch has happened

It's also gotten a new async keyword which can be put in front of a function, to declare that it may give up control.

Inside an async function declaration, you are allowed to use the await keyword. You write this in front of another async operation. When execution reaches this point, we schedule that operation and yield control.

Asyncronous Iteration

We can make an asynchronous version of an iterable, which will have a Symbol.asyncIterator method on it to make an asynchronous iterator.

The next() method on this asynchronous iterator is asynchronous and returns Promises, where a normal iterator just returns values straight away.

This means that we can use this next method together with the await keyword.

There is also a new for-await-of loop which mirrors the for-of loop, and lets us easily loop over an asynchronous iterable.

Asynchronous iterators can also take advantage of the yield* someOtherGenerator(); syntax.

(These features only available in NodeJS 10 and later, or NodeJS 9 with the harmony flag node --harmony my-file.js.)

async function* myAsyncThing() {
    yield 1;
    yield 2;
    yield 3;
}

const it = myAsyncThing(),
      itFn = (d) => {
          console.log("Fiddly manual async iteration", d);
          return it.next();
      };

const finalPromise = it.next().then(itFn).then(itFn).then(itFn);

async function awaitExample() {
    const iterable2 = myAsyncThing();
    console.log("Using 'await'", await iterable2.next());
    console.log("Using 'await'", await iterable2.next());
    console.log("Using 'await'", await iterable2.next());

    for await (const x of myAsyncThing()) {
        console.log("Using 'for await'", x);
    }
}

const finalFunctionPromise = awaitExample();

Destructuring

You can use ... to mean "all the remanining properties" on either the left-hand side of an assignment, in an object or array literal, or in function arguments.

This is called gathering and spreading depending on context, since it can either destructure an object or array, or it can flatten it to make construction more succinct.

// Array destructuring — works on any iterable
const arr = [1, 2, 3, 4, 5];
const [first, second, ...rest] = arr;

console.log("Array destructured into variables", first, second, rest);
console.log("Variables flattened into array", [first, second, ...rest]);
(function(a, b, ...c) {
    console.log("Function rest argument", a, b, c);
})(1, 2, 3, 4);

// Object destructuring
const thing = {
    "x": 1,
    "this": 2,
    "that": 3,
    "t'other": 4
};

const {x, ...whatever} = thing;

console.log("Object destructured into variables", x, whatever);
console.log("Variables flattened into object", {x, ...whatever});
(function(a, b, {x, y, ...others}) {
    console.log("Function object destructuring", a, b, x, y, others);
})(-1, 0, {x: 1, y: 2, feh: 3, bleh: 4, meh: 5});
Array destructured into variables 1 2 [ 3, 4, 5 ]
Variables flattened into array [ 1, 2, 3, 4, 5 ]
Function rest argument 1 2 [ 3, 4 ]
Object destructured into variables 1 { this: 2, that: 3, 't\'other': 4 }
Variables flattened into object { x: 1, this: 2, that: 3, 't\'other': 4 }
Function object destructuring -1 0 1 2 { feh: 3, bleh: 4, meh: 5 }

It's obviously possible to make a bit of a mess here, but it also makes the option object pattern a bit neater.

There's also a tiny bit of extra helpful syntax for when you want to make a key with the same name and value as some other variable:

const thing = {x: 1, y: 2};
console.log("Inserting variable into a key of the same name", {thing});

// This is helpful for cloning objects:
console.log("Cloned", thing);
Inserting variable into a key of the same name { thing: { x: 1, y: 2 } }
Cloned { x: 1, y: 2 }

JavaScript Testing

I quite like tape.