Photo by Christopher Robin Ebbinghaus on Unsplash

JavaScript: The Good Parts

Quang Trong VU
25 min readJul 7, 2020

--

Some point in “The Good Parts — Douglas Crockford”

  1. Literals
  2. Objects
  3. Functions
  4. Inheritances
  5. Some of what’s new in ES6, ECMA 2016, ECMA 2017, ECMA 2018

1. Literals

- type of literals in JavaScript: number, string, object, array, function, regex.

2. Objects

Boolean/Integer/Strings are object-like because they have methods but are immutable.

Objects are the mutable keyed collections.

Class-free means that you don’t need to create a class before initializing an object.

Prototype linkage feature allows one object inherits the properties of another

Object value retrieval: by constants [] or by dot method (.)

Update property of an object: update the property’s value if it existed, or add new property if it hasn’t existed yet.

Objects are passed by reference.

Prototype: every object is linked to a prototype object from which it can inherit properties.

  • if an object is created by the object literal, so it’s prototype is Object.prototype.
var foo = { name: 'Moo' };
  • we will create a new method create for Object, so we can select the object that should be the prototype of the new object.
if (typeof Object.create !== ‘function’) {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
};
var another_foo = Object.create(foo);
  • The prototype link has no effect on updating. When we make changes to an object, the object’s prototype is not touched.
  • The prototype link is used only in retrieval. If we try to retrieve a property value from an object, and if the object lacks the property name, then JavaScript attempts to retrieve the property value from the prototype object. And if that object is lacking the property, then it goes to its prototype, and so on until the process finally bottoms out with Object.prototype. If the desired property exists nowhere in the prototype chain, then the result is the undefined value. This is called delegation.
  • The prototype relationship is a dynamic relationship. If we add a new property to a prototype, that property will immediately be visible in all of the objects that are based on that prototype.

Reflection: Some care must be taken because any property on the prototype chain can produce value. You can prevent this issue by filtering function values out of the search result or use hasOwnProperty method to check the existing of property in the current object.

Enumeration: You should know about “for … in” that loops through all object’s property names.

Delete: The delete operator can be used to remove a property of an object. It will remove a property from the object if it has one. It will not touch any of the objects in the prototype linkage. So this is a change for prototype linkage’s property shines through.

Global Abatement: JavaScript makes it easy to define global variables that can hold all of the assets of your application. Unfortunately, global variables weaken the resiliency of programs and should be avoided. One way to minimize the use of global variables is to create a single global variable for your application.

var DEMOAPP = {};
DEMOAPP.foo = ...
DEMOAPP.bar = ...

3. Functions

Function Objects

  • Functions in JavaScript are objects.
  • Function objects are linked to Function.prototype which is itself linked to Object.prototype.
  • Every function is also created with two additional hidden properties: the function’s context and the code that implements the function’s behavior.
  • Every function object is also created with a prototype property. Its value is an object with a constructor property whose value is the function. This is distinct from the hidden link to Function.prototype.
call the function: not method, not new prefix

You can see in the image:

barFunc:
- Function.prototype is barFunc.__proto__
- prototype is barFunc.prototype: prototype.constructor’s value = f (name) (the function was declared above)

Function Literal
Functions can be defined inside of other functions. An inner function, of course, has access to its parameters and variables. An inner function also enjoys access to the parameters and variables of the functions it is nested within.
The function object created by a function literal contains a link to that outer context. This is called closure. This is the source of enormous expressive power.

Invocation
Every function receives two additional parameters: this, arguments. The this parameter’s value is different base on the invocation pattern. There are four patterns of invocation in JavaScript: the method invocation pattern, the function invocation pattern, the constructor invocation pattern and the apply invocation pattern.

(1) The Method Invocation Pattern: this is bound to that object (eg: fooObject in the example below).
The binding of this to the object happens at invocation time. This very late binding makes functions that use this highly reusable. Methods that get their object context from this are called public methods.

var fooObject = {
value: 0,
increment: function (inc) {
this.value += typeof inc === 'number' ? inc : 1;
}
};
fooObject.increment();
document.writeln(fooObject.value); // 1
fooObject.increment(2);
document.writeln(fooObject.value); // 3
  • (2) The Function Invocation Pattern
    If a function is not the property of an object, then it is invoked as a function. In this case the this is bound to the global object (Window object in browser). The expectation is that when the inner function is invoked, this would still be bound to the this variable of the outer function. Solution for this case: the method defines a variable and assigns it the value of this, the inner function will have access to this through that variable.
fooObject.double = function () {
var that = this; // Workaround.
var helper = function () {
that.value = add(that.value, that.value);
};
helper(); // Invoke helper as a function.
};
// Invoke double as a method.
fooObject.double();
document.writeln(fooObject.getValue());
  • (3) The Constructor Invocation Pattern
    JavaScript is a prototypal inheritance language. This means that objects can inherit properties directly from another object. If a function is invoked with the new prefix, then a new object will be created with a hidden link to the value of the function’s prototype member, and this will be bound to that new object.
the return object with invocation with new prefix
// Create a constructor function called Boo.
// It makes an object with a status property.
var Boo = function (string) { this.status = string; };
// Give all instances of Boo a public method
// called get_status
Boo.prototype.get_status = function () {
return this.status;
};
// Make an instance of Boo.
var myBoo = new Boo("I dont know");
document.writeln(myBoo.get_status()); // I dont know

Functions that are intended to be used with the new prefix are called constructors. By convention, they are kept in variables with a capitalized name. If a constructor is called without the new prefix, very bad things can happen without a compile-time or runtime warning, so the capitalization convention is really important.

  • (4) The Apply Invocation Pattern
    Because JavaScript is a functional object-oriented language, functions can have methods. The apply method lets us construct an array of arguments to use to invoke a function. It also lets us choose the value of this. The apply method takes two parameters. The first is the value that should be bound to this. The second is an array of parameters.
// Make an array of 2 numbers and add them.
var array = [4, 5];
var sum = add.apply(null, array); // sum is 9
// Make an object with a status member.
var statusObject = { status: 'InProgress' };
// statusObject does not inherit from Boo.prototype,
// but we can invoke the get_status method on
// statusObject even though statusObject does not have
// a get_status method.
var status = Boo.prototype.get_status.apply(statusObject); // status is 'InProgress'

Arguments
A bonus parameter that is available to functions when they are invoked is the arguments array.

Return
always: undefined (if not specify).
If the function was invoked with the new prefix and the return value is not an object, then this (the new object) is returned instead.

Augmenting Types
JavaScript allows the basic types of language to be augmented.
You can augment to functions, arrays, strings, numbers, regular expression, booleans.

Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
// Applying
// integer
Number.method('integer', function() {
return Math[this < 0 ? 'ceiling' : 'floor'](this);
});
document.writeln((-10 / 3).integer()); // -3
// trim
String.method('trim', function() {
return this.replace(/^\s+|\s+$/g, '');
});
document.writeln('"' + " neat . ".trim() + '"');
// Add a method conditionally
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
};

Recursion

// Hanoi tower
var hanoi = function (disc, src, aux, dst) {
if (disc > 0) {
hanoi(disc - 1, src, dst, aux);
document.writeln('Move disc ' + disc + ' from ' + src + ' to ' + dst);
hanoi(disc - 1, aux, src, dst);
}
}
// factorial
var factorial = function (a) {
let i = a || 1;
if ( i < 2 ) {
return i;
}
return i * factorial (a - 1);
}

Scope

  • Scope in a programming language controls the visibility and lifetimes of variables and parameters.
  • Most languages in C syntax have block scope. All variables defined in a block are not visible from outside of the block. The variables defined in a block can be released when execution of the block is finished.
  • JavaScript does not have block scope even though its block syntax suggests that it does. JavaScript does have function scope. That means that the parameters and variables defined in a function are not visible outside of the function and that a variable defined anywhere within a function is visible everywhere within the function.

Closure

  • The inner functions get access to the parameters and variables of the functions they are defined within (with the exception of this and arguments).
  • One interesting case is when the inner function has a longer lifetime than its outer function.
// sample 1
var fooObject = function () {
var value = 0;
return {
increment: function (inc) {
value += typeof inc === 'number' ? inc : 1;
},
getValue: function () {
return value;
}
};
}();
// sample 2
// Create a maker function called boo. It makes an
// object with a get_status method and a private
// status property.
var boo = function (status) {
return {
get_status: function () {
return status;
}
};
};
// Make an instance of boo
var myBoo = boo("amazed");
document.writeln(myBuo.get_status());
// sample 3
// Define a function that sets a DOM node's color
// to yellow and then fades it to white.
var fade = function (node) {
var level = 1;
var step = function () {
var hex = level.toString(16);
node.style.backgroundColor = '#FFFF' + hex + hex;
if (level < 15) {
level += 1;
setTimeout(step, 100);
}
};
setTimeout(step, 100);
};
fade(document.body);
// Sample 4
// BAD EXAMPLE
// Make a function that assigns event handler functions to an array of nodes the wrong way.
// When you click on a node, an alert box is supposed to display the ordinal of the node.
// But it always displays the number of nodes instead.
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(e);
};
}
};
// BAD EXAMPLE
// BETTER EXAMPLE
// Make a function that assigns event handler functions to an array of nodes the right way
// When you click on a node, an alert box will display the ordinal of the node.
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
return function (e) {
alert(e);
};
}(i);
}
}
// BETTER EXAMPLE

Callbacks

  • Functions can make it easier to deal with discontinuous events.
request = prepare_the_request();
send_request_async(request, function (response) {
display(response);
});

Module

  • We can use functions and closure to make modules. A module is a function or object that presents an interface but that hides its state and implementation. By using functions to produce modules, we can almost completely eliminate our use of global variables, thereby mitigating one of JavaScript’s worst features.
  • Suppose we want to augment String with a deentityify method. Its job is to look for HTML entities in a string and replace them with their equivalents. It makes sense to keep the names of the entities and their equivalents in an object. But where should we keep the object? We could put it in a global variable, but global variables are evil. We could define it in the function itself, but that has a runtime cost because the literal must be evaluated every time the function is invoked. The ideal approach is to put it in a closure, and perhaps provide an extra method that can add additional entities:
String.method('deentityify', function() {
// The entity table. It maps entity names to characters.
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
// Return the deentityify method.
return function () {
// This is the deentityify method. It calls the string
// replace method, looking for substrings that start
// with '&' and end with ';'. If the characters in between
// are in the entity table, then replace the entity
// with the character from the table. It uses
// a regular expression
return this.replace(/&([^&;]+);/g,
function (a, b) {
var r = entity[b];
return typeof r === 'string' ? r : a;
}
);
};
}());

another example

var serial_maker = function () {
// Produce an object that produce unique string.
// A unique string is made up of two parts: a prefix
// and a sequence number. The object comes with methods
// for setting the prefix and sequence number, and a
// gensym method that produces unique string.
var prefix = '';
var seq = 0;
return {
set_prefix: function (p) {
prefix = String(p);
},
set_seq: function (s) {
seq = s;
},
gensym: function () {
var result = prefix + seq;
seq += 1;
return result;
}
};
};
var seqer = serial_maker();
seqer.set_prefix = ('Q');
seqer.set_seq = (1000);
var unique = seqer.gensym(); // unique is "Q1000"

Cascade

  • If we have methods which return this, we can enable cascades.

4. Inheritances

Pseudoclassical

Instead of having objects inherit directly from other objects, an unnecessary level of indirection is inserted such that objects are produced by constructor functions. When a function object is created, the Function constructor that produces the function object runs some code like this:

this.prototype = { constructor: this };

Every function gets a prototype object because the language does not provide a way of determining which functions are intended to be used as constructors.

When a function is invoked with the constructor invocation pattern using the new prefix, this modifies the way in which the function is executed. If the new operator were a method instead of an operator, it could have been implemented like this:

Function.method('new', function() {
// Create a new object that inherits from the
// constructor's prototype
var that = Object.create(this.prototype);
// Invoke the constructor, binding -this to
// the new object.
var other = this.apply(that, arguments);
// If its return value isn't an object,
// substitue the new object.
return (typeof other === 'object' && other) || that;
});

We can define a constructor and augment its prototype

var Mammal = function (name) {
this.name = name;
};
Mammal.prototype.get_name = function () {
return this.name;
};
Mammal.prototype.says = function () {
return this.saying || '';
};
// Now, we can make an instance
var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name(); // 'Herb the Mammal'
// We can make another pseudoclass that inhertis
// from Mammal by defining its constructor function
// and replaceing its prototype with an instance of Mammal
var Cat = function (name) {
this.name = name;
this.saying = 'meow';
};
// Replace Cat.prototype with a new instance of Mammal
Cat.prototype = new Mammal();
// Augment the new prototype with
// purr and get_name methods
Cat prototype.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
Cat.prototype.get_name = function () {
return this.say() + ' ' + this.name + ' ' + this.say();
};
var myCat = new Cat('Henrietta');
var says = myCats.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r'
var name = myCat.get_name(); // 'meow Henrietta meow'
// We can hide some of ugliness by using the method and defining
// an inherits method:
Function.method('inherits', function (Parent) {
this.prototype = new Parent();
return this;
});
// Our inherits and method methods return this, allowing
// us to program in a cascade style. We can now make our
// Cat with one statement
var Cat = function (name) {
this.name = name;
this.saying = 'meow';
}.
inherits(Mammal).
method('purr', function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
}).
method('get_name', function() {
return this.says() + ' ' + this.name + ' ' + this.says();
});

There is no privacy; all properties are public. There is no access to super methods. Even worse, there is a serious hazard with the use of constructor functions. If you forget to include the new prefix when calling a constructor function, then this will not be bound to a new object. Sadly, this will be bound to the global object, so instead of augmenting your new object, you will be clobbering global variables. That is really bad. There is no compile warning, and there is no runtime warning. This is a serious design error in the language. To mitigate this problem, there is a convention that all constructor functions are named with an initial capital and that nothing else is spelled with an initial capital. This gives us a prayer that visual inspection can find a missing new. A much better alternative is to not use new at all (Module Pattern — Function below?)

Object Specifiers

bad sample:

var myObject = maker(f, l, m, c, s);

good sample:

var myObject = maker({
first: f,
last: l,
state: s,
city: c
});

Prototypal

Prototypal inheritance is conceptually simpler than classical inheritance.

var myMammal = {
name: 'Herb the Mammal',
get_name: function () {
return this.name;
},
says: function () {
return this.saying || '';
}
};

We can make more instances with the Object.create method.

var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
myCat.get_name = function () {
return this.says() + ' ' + this.name + ' ' + this.says();
}

Functional

One weakness of the inheritance pattern that we have seen so far is that it no has privacy. We can resolve this by module pattern as follows:

We will create a function that produces objects. The function has four steps:

  1. It creates a new object. (by object literal, by constructor function, by Object.create or by a function that returns an object)
  2. It optionally defines private instance variables and methods. (These are actually just normal vars of the function.
  3. It augments the new object (in step (1)) with methods. Those methods will have privileges access to the parameters of the function and the vars were defined in the step (2).
  4. It returns that new object.

Now we can keep the privacy of members that were defined in step 2.

Here is a pseudocode template for a functional constructor:

// lower case: myConstructor cause no use "new" prefix
// spec: is an object contains the info that constructor needs to
// make an instance
// my: is a container of secrets that are shared by the
// constructors in the inheritance chain.
var myConstructor = function (spec, my) {
// step 2
// private instance variables;
// private instance methods;
// the expected return object
var that;
// the container of share secrets between inheritance chain
my = my || {};
// Add shared variables and functions to my
// step 1
// We can pass spec and my to construct this new object
that = a new object (create by whatever way you want)
// step 3
// Add privileged methods to that
// step 4
return that;
};

Let’s apply this pattern to our mammal example (ignore my parameter):

var mammal = function (spec) {
var that = {};
that.get_name = function () {
return spec.name;
};
that.says = function () {
return saying || '';
};
return that;
};
var myMammal = mammal({ name: 'Herb' });

In the pseudoclassical pattern, the Cat constructor function had to duplicate work that was done by the Mammal constructor. That isn’t necessary for the function because the Cat constructor will call the Mammal constructor, letting Mammal do most of the work of object creation, so Cat only has to concern itself with the differences:

var cat = function (spec) {
spec.saying = spec.saying || 'meow';
var that = mammal(spec);
that.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
that.get_name = function () {
return that.says() + ' ' + spec.name + ' ' + that.says();
}
return that;
};
var myCat = cat({ name: 'Henrietta' });

The functional pattern also gives us a way to deal with super methods. We will make a superior method that takes a method name and returns a function that invokes that method. The function will invoke the original method even if the property is changed.

Object.method('superior', function (name) {
var that = this;
var method = that[name];
return function () {
return method.apply(that, arguments);
};
});

Let’s try it out on a coolcat that is just like cat except it has a cooler get_name method that calls the super method. It requires just a little bit of preparation. We will declare a super_get_name variable and assign it the result of invoking the superior method:

var coolcat = function (spec) {
var that = cat(spec);
var super_get_name = that.superior('get_name');
that.get_name = function (n) {
reuturn 'like ' + super_get_name() + ' baby';
};
return that;
};
var myCoolCat = coolcat({name: 'Bix'});
var name = myCoolCat.get_name(); // 'like meow Bix meow baby'

The functional pattern has a great deal of flexibility. It requires less effort than the pseudoclassical pattern, and gives us better encapsulation and information hiding and access to super methods.

Parts

We can compose objects out of sets of parts. For example, we can make a function that can add simple event processing features to any object. It adds an on method, a fire method, and a private event registry.

var eventuality = function (that) {
var registry = {};
that.fire = function (event) {
// Fire an event on an object. The event can be either
// a string containing the name of the event or an object
// containing a type property containing the name of event.
// Handlers registered by the 'on' method that match the
// event name will be invoked.
var array;
var func;
var handler;
var i;
var type = typeof event === 'string' ? event : event.type;
// If an array of handlers exist for this event, then
// loop throught it and execute the handlers in order.
if (registry.hasOwnProperty(type)) {
array = registry[type];
for (i=0; i < array.length; i += 1) {
handler = array[i];
// A handler record contains a method and an optional
// array of parameters. If the method is a name, look
// up the function.
func = handler.method;
if (typeof func === 'string') {
func = this[func];
}
// Invoke a handler. If the record contained parameters,
// then pass them. Otherwise, pass the event object.

func.apply(this, handler.parameters || [event]);
}
}
return this;
};
that.on = function (type, method, parameters) {
// Register an event. Make a handler record. Put it
// in a handler array, making one if it doesn't yet
// exist for this type.
var handler = {
method: method,
parameters: parameters
};
if (registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registery[type] = [handler];
}
return this;
};
return that;
};

We could call eventuality on any individual object, best owning it with event handling methods. We could also call it in a constructor function before that is return:

eventuality(that);

In this way, a constructor could assemble objects from a set of parts. JavaScript’s loose typing is a big benefit bere because we are not burdened with a type system that is concerned about the lineage of classes. Instead, we can focus on the character of their contents.

If we wanted eventuality to have access the object’s private state, we could pass it the my bundle.

5. Some of what’s new in ES6, ECMA 2016, ECMA 2017, ECMA 2018

ES6

  • Arrow functions
  • Promises
  • Generators
  • let and const
  • Classes
  • Modules
  • Multiline strings
  • Template literals
  • Default Parameters
  • The spread operator |
  • Destructing assignments
  • Enhanced object literals
  • The for .. of loop
  • Map and Set

Arrow function

A new this scope: the this scope with arrow functions is inherited from the context.
With regular function, this always refers to the nearest function, while with arrow functions this problems is removed, and you won’t need to write var that = this ever again.

Promises

Promises allow us to eliminate the famous “callback hell”, although they introduce a bit more complexity (which has been solved in ES2017 with asyn a higher level construct).

Promises have been used by JavaScript developers in jQuery, q, deferred.js, vow…

Code demo without promises

setTimeout(function() {
console.log('I promised to run after 1s');
setTimeout(function() {
console.log('I promised to run after 2s')
}, 1000);
}, 1000);

as

const wait = () => new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
wait()
.then(() => {
console.log('I promised to run after 1s');
return wait();
})
.then(() => console.log('I promised to run after 2s'));

let and const

var is traditionally function scoped.

let is a new variable declaration which is block scoped.

const is just like let , but immutable.

Class

class Person {
constructor (name) {
this.name = name;
}

hello() {
return 'Hello, I am ' + this.name + '.';
}
}
class Actor extends Person {
hello() {
return super.hello() + ' I am an actor.';
}
}
var tomCruise = new Actor('Tom Cruise');
tomCruise.hello(); // Hello, I am Tom Cruise. I am an actor.

Constructor

Classes have a special method called constructor which is called when a class is initialized via new

Super

The parent class can be referenced using super()

Getters and Setters

A getter for a property can be declared as

class Person {
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}

Setters are written in the same way

class Person {
set age(years) {
this.theAge = years;
}
}

Modules

Before ES5, there were at least 3 major competing module standards,which fragmented the community:

  • AMD
  • RequireJS
  • CommonJS

ES6 Importing module

import * from 'mymodule'
import React from 'react'
import { React, Component } from 'react'
import React as MyLibrary from 'react'

Exporting modules

export var foo = 2
export function bar() { /* */ }

The spread operator

You can expand an array, an object or a string using the spread operator ...

// Array
const a = [1, 2, 3];
const b = [...a, 4, 5, 6]; // [1, 2, 3, 4, 5, 6]
const c = [...a]; // [1, 2, 3]
// Object
const newObj = { ...oldObj }
// String
const hey = 'hey';
const arrayized = [...hey]; // ['h', 'e', 'y']
// to params
const f = (foo, bar) => {};
const a = [1, 2];
f(...a); // the old way: f.apply(null, a);

Destructing assignments

Given an object, you can extract just some values and put them into named variables

// Object
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54
};
const { firstName: name, age } = person;
// Array
const a = [1, 2, 3, 4, 5];
[first, second, , , fifth] = a;

Enhanced Object Literals

Simple syntax to include variables

// old
const something = 'y';
const x = {
something: something,
};
// new
const something = 'y';
const x = {
something
};

Prototype

A prototype can be specified with

const anObject = { y: 'y' }
const x = {
__proto__: anObject;
};

super()

const anObject = { y: 'y', test: () => 'zoo' };
const x = {
__proto__: anObject,
test() {
return super.test() + 'x';
}
};
x.test(); // zoox

Dynamic properties

const x = {
['a' + '_' + 'b']: 'z'
}
x.a_b // z

For-of loop

ES5 back in 2009 introduced forEach() loops. While nice, they offered no way to break, like for loops always did.

ES6 introduced the for-of loop, which combines the conciseness of forEach with the ability to break.

// iterate over the value
for (const v of ['a', 'b', 'c']) {
console.log(v);
}
// get the index as well, using `entries()`
for (const [i, v] of ['a', 'b', 'c'].entries()) {
console.log(i, v);
}

Map and Set

Map and Set (and their respective garbage collected WeakMap and WeakSet) are the official implementations of two very popular data structures (introduced later on).

The ES2016 improvements

  • Array.prototype.includes
  • Exponentiation Operator

Array.prototype.includes()

// old
if (![1, 2].indexOf(3)) {
console.log('Not found');
}
// new
if (![1, 2].includes(3)) {
console.log('Not found');
}

Exponentiation Operator

Math.pow(4, 2) == 4 ** 2;

The ES2017 improvements

  • String padding
  • Object.values
  • Object.entries
  • Object.getOwnPropertyDescriptors()
  • Trailing commas in function parameter lists and calls
  • Async functions
  • Shared memory and atomics

String padding

padStart(targetLength [, padString]);
padEnd(targetLength [, padString]);

Object.values()

This method returns an array containing all the object own property values.

const person = { name: 'Fred', age: 87 }
Object.values(person); // ['Fred', 87]

Object.values() also works with arrays:

const people = ['Fred', 'Tony'];
Object.values(people); // ['Fred', 'Tony']

Object.entries()

This method returns an array containing all the object own properties, as an array of [key, value] pairs

const person = { name: 'Fred', age: 87 };
Object.entries(person); // [['name', 'Fred'], ['age', 87]]
// array
const people = ['Fred', 'Tony'];
Object.entries(people); // [['0', 'Fred'], ['1', 'Tony']]

getOwnPropertyDescriptors()

This method returns all own (non-inherited) property descriptors of an object.

An object in JavaScript has a set of properties, and each of these properties has a descriptor.

A descriptor is a set of attributes of a property, and it’s composed by a subset of the following:

  • value: the value of the property
  • writable: true the property can be changed
  • get: a getter function for the property, called when the property is read
  • set: a setter function for the property, called when the property is set to a value
  • configurable: if false, the property cannot be removed nor any attribute can be changed, except its value
  • enumerable: true if the property is enumerable

Trailing commas

This feature allows to have trailing commas in function declarations, and in functions calls:

const doSomething = (var1, var2, ) => {
// ...
}
doSomething('test2', 'test2', );

Async functions

ES2017 introduced the concept of async functions, and it’s the most important change introduced in this ECMAScript edition.

Async functions are a combination of promises and generators to reduce the boilerplate around promises, and the “don’t break the chain” limitation of chaining promises.

function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 3000);
});
}
async function doSomething() {
console.log(await doSomethingAsync());
}
console.log('Before');
doSomething();
console.log('After');
// output
Before
After
I did something // after 3s

multiple async functions in series

function promiseToDoSomething() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 1000);
});
}
async function watchOverSomeoneDoingSomething() {
const something = await promiseToDoSomething();
return something + ' and I watched';
}
async function watchOverSomeoneWatchingSomeoneDoingSomething() {
const something = await watchOverSomeoneDoingSomething()
return something + ' and I watched as well';
}
watchOverSomeoneWatchingSomeoneDoingSomething().
then((res) => { console.log(res); });

Shared Memory and Atomics

WebWorkers are used to create multithreaded programs in the browser.

They offer a messaging protocol via events. Since ES2017, you can create a shared memory array between web workers and their creator, using a SharedArrayBuffer

Since we don’t know how much time writing to a shared memory portion takes to propagate, Atomics are a way to enforce that when reading a value and any kind of writing operation is completed.

The ES2018 improvements

Rest/Spread Properties

ES6 introduced the concept of a rest element when working with an array destructing:

const numbers = [1, 2, 3, 4, 5];
[first, second, ...others] = numbers;

and spread elements

const numbers = [1, 2, 3, 4, 5];
const sum = (a, b, c, d, e) => a + b + c + d + e;
const sum = sum(...numbers);

ES2018 introduces the same but for objects

Rest properties

const { first, second, ...others } = { first: 1, second: 2, third: 3, fourd: 4, fifth: 5 };
first // 1
second // 2
others // { third: 3, fourth: 4, fifth: 5 }

Spread properties allow to create a new object by combining the properties of the object passed after the spread operator:

const items = { first, second, ...others };
items // { first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }

Asynchronous iteration

The new construct for-await-of allows you to use an async iterable object as the loop iteration:

for await (const line of readLines(filePath)) {
console.log(line);
}

Since this uses await , you can use it only inside async functions, like a normal await

Promise.prototype.finally()

When a promise is fulfilled, successfully it calls the then() methods, one after another.

If something fails during this, the then() methods are jumped and the catch() method is executed.

finally() allow you to run some code regardless of the successful or unsuccessful execution of the promise

fetch('file.json')
.then(data => data.json())
.catch(error => console.error(error))
.finally(() => console.log('finished'))

Regular Expression improvements

RegExp lookbehind assertions: match a string depending on what precedes it.

This is a lookahead: you use ?= to match a string that’s followed by a specific substring:

/Roger(?=Waters)/
/Roger(?= Waters)/.test('Roger is my dog'); // false
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician'); // true

?! perform the inverse operation, matching if a string is not followed by a specific substring:

/Roger(?!Waters)/
/Roger(?! Waters)/.test('Roger is my dog'); // true
/Roger(?! Waters)/.test('Roger Waters is a famous musician'); // false

Lookahead use the ?= symbol. They were already available.

Lookbehinds, a new feature, use ?<=

/(?<=Roger) Waters/
/(?<=Roger) Waters/.test('Pink Waters is my dog'); // false
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician'); // true

A lookbehind is negated using ?<!

/(?<!Roger) Waters/
/(?<!Roger) Waters/.test('Pink Waters is my dog'); // true
/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician'); // false

Unicode property escapes \p{...} and \P{...}

In a regular expression pattern you can use \d to match any digit, \s to match any character that’s not a white space, \w to match any alphanumeric character, and so on.

This new feature extends this concept to all Unicode characters introducting \p{} and is negation \P{}

Any unicode character has a set of properties. For example, Script determines the language family, ASCII is a boolean that’s true for ASCII characters, and so on. You can put this property in the graph parentheses, and the regex will check for that to be true.

/^\p{ASCII}+$/u.test('abc'); //
/^\p{ASCII}+$/u.test('ABC@'); //
/^\p{ASCII}+$/u.test('ABC..'); //

ASCII_Hex_Digit is another boolean property, that checks if the string only contains valid hexadecimal digits:

/^\p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF'); //
/^\p{ASCII_Hex_Digit}+$/u.test('h');

There are many other boolean properties, which you just check by adding their name in the graph parentheses, including Uppercase, Lowercase , White_Space, Alphabetic , Emoji and more:

/^\p{Lowercase}$/u.test('h');
/^\p{Uppercase}$/u.test('H');
/^\p{Emoji}+$/u.test('H');
/^\p{Emoji}+$/u.test('...');

In addition to those binary properties, you can check any of the unicode character properties to match a specific value. In this example, I check if the string is written in the Greek or Latin alphanet.

/^\p{Script=Gree}+$/u.test('....');
/^\p{Script=Latin}+$/u.test('hey');

Named capturing groups

In ES2018 a capturing group can be assigned to a name, rather than just being assigned a slot in the result array:

const re /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}/
const result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

The s flag for regular expression

The s flag, short for single line, cause the . to match new line characters as well. Without it, the dot matches regular characters but not the new line:

/hi.welcome/.test('hi\nwelcome') // false
/hi.welcome/s.test('hi\nwelcome') // true

Hope this help!

--

--

Quang Trong VU

Software Developer — Life’s a journey — Studying and Sharing