Lesson 10-New Features in ES6~ES10 Async, Class, Module

async/await

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
  console.log(result);
});
// Dynamic import() loading
const strings = await import(`/i18n/${navigator.language}`);
// Database operations
const connection = await dbConnector();
// Fallback dependency
let jQuery;
try {
  jQuery = await import('https://cdn-a.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.com/jQuery');
}

Class

Basic Class Usage

class MyClass {
  static PI = 22 / 7;
  static #totallyRandomNumber = 4;
  static #computeRandomNumber() {
    return MyClass.#totallyRandomNumber;
  }
  static random() {
    console.log('I heard you like random numbers…');
    return MyClass.#computeRandomNumber();
  }
  constructor() {
    this._count = 0;
    // ...
  }
  static classMethod() {
    return 'hello';
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: ' + value);
  }
  increment() {
    this._count++;
  }
}
MyClass.classMethod(); // Static method call
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'

Class Inheritance

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  static print() {
    console.log(this.x);
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    // this.color = color; // ReferenceError
    super(x, y);
    super.print();
    this.color = color; // Correct
  }
}
class VersionedArray extends Array {
  constructor() {
    super();
    this.history = [[]];
  }
  commit() {
    this.history.push(this.slice());
  }
  revert() {
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}
var x = new VersionedArray();
x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]
x.commit();
x.history // [[], [1, 2]]
x.push(3);
x // [1, 2, 3]
x.history // [[], [1, 2]]
x.revert();
x // [1, 2]

Static Properties and Methods

In JavaScript, static properties and methods belong to the class itself, not its instances. They are typically used for functionality or state related to the class as a whole, independent of specific instances.

Static Properties

Static properties are declared with the static keyword in the class definition:

class MyClass {
  static myStaticProperty = 'someValue';
}

console.log(MyClass.myStaticProperty); // Output: "someValue"

Static properties are accessed directly via the class name without instantiating the class. They are shared across all references to the class.

Static Methods

Static methods are also declared with the static keyword and cannot access instance properties or methods since they are not tied to any specific instance:

class MyClass {
  static myStaticMethod() {
    console.log('This is a static method.');
  }
}

MyClass.myStaticMethod(); // Output: "This is a static method."

Static methods are often used for utility functions, factory methods, or logic related to class initialization or configuration, operating on the class itself rather than instance state.

Extending Built-in Classes

JavaScript allows extending built-in classes (e.g., Array, Date, Error) through subclassing, enabling custom versions with added methods or modified behavior:

class CustomArray extends Array {
  // Add a static method
  static createWithLength(length, initialValue) {
    const arr = new this(length);
    for (let i = 0; i < length; i++) {
      arr[i] = initialValue;
    }
    return arr;
  }

  // Add an instance method
  sum() {
    return this.reduce((acc, val) => acc + val, 0);
  }
}

const myArray = CustomArray.createWithLength(5, 1); // Create array with static method
console.log(myArray.sum()); // Sum using custom instance method

CustomArray extends Array, adding a static method createWithLength and an instance method sum.

Class Checking: instanceof

The instanceof operator checks if an object is an instance of a class (or its derived classes) by examining whether the class’s prototype appears in the object’s prototype chain:

class Base {}
class Derived extends Base {}

const baseInstance = new Base();
const derivedInstance = new Derived();

console.log(baseInstance instanceof Base); // Output: true
console.log(derivedInstance instanceof Base); // Output: true, since Derived inherits from Base
console.log(derivedInstance instanceof Derived); // Output: true

console.log(baseInstance instanceof Derived); // Output: false, Base instance is not a Derived instance

The instanceof operator is useful for verifying an object’s class, especially in inheritance scenarios. It also respects the class’s Symbol.hasInstance static method, which, if defined, determines the result of instanceof checks.

Mixin Pattern Implementation

A Mixin combines multiple objects into a new object that inherits the interfaces of its components. A simple implementation is as follows:

function mix(...mixins) {
  class Mix {
    constructor() {
      for (let mixin of mixins) {
        copyProperties(this, new mixin()); // Copy instance properties
      }
    }
  }
  for (let mixin of mixins) {
    copyProperties(Mix, mixin); // Copy static properties
    copyProperties(Mix.prototype, mixin.prototype); // Copy prototype properties
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if (key !== 'constructor' && key !== 'prototype' && key !== 'name') {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

Module

CommonJS

// CommonJS module
let { stat, exists, readfile } = require('fs');
// Equivalent to
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

// ES6 module
import { stat, exists, readFile } from 'fs';
import * as circle from './circle';

import()

const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
  .then(module => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });

// On-demand loading
button.addEventListener('click', event => {
  import('./dialogBox.js')
    .then(dialogBox => {
      dialogBox.open();
    })
    .catch(error => {
      /* Error handling */
    });
});

// Conditional loading
if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}

// Dynamic module paths
import(f())
  .then(...);

async function main() {
  const myModule = await import('./myModule.js');
  const { export1, export2 } = await import('./myModule.js');
  const [module1, module2, module3] = await Promise.all([
    import('./module1.js'),
    import('./module2.js'),
    import('./module3.js'),
  ]);
}
main();

export

// Export variables
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };

// Export functions
export function multiply(x, y) {
  return x * y;
};

// Export with renaming
function v1() { ... }
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion,
};

// Default export
export default function foo() {
  console.log('foo');
}
// Or
function foo() {
  console.log('foo');
}
export default foo;

Differences Between ES6 Modules and CommonJS

There are two major differences:

  • CommonJS modules output a copy of a value, while ES6 modules output a reference to a value.
  • CommonJS modules are loaded at runtime, while ES6 modules export interfaces at compile time.

Node.js Loading

Node.js’s handling of ES6 modules is complex due to its own CommonJS module format, which is incompatible with ES6 modules. The current solution is to keep them separate, with ES6 modules and CommonJS using distinct loading mechanisms.

Node.js requires ES6 modules to use the .mjs file extension. Files with import or export commands must use .mjs, and Node.js treats them as ES6 modules, enabling strict mode by default without needing "use strict".

To avoid using .mjs, you can set the type field to "module" in the project’s package.json:

{
  "type": "module",
  "main": "./src/index.js", // Specifies the module entry point
  "exports": {
    "./submodule": "./src/submodule.js" // Aliases src/submodule.js as submodule
  }
}

Loading CommonJS Modules in ES6 Modules

{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "require": "./index.cjs",
    "default": "./wrapper.mjs"
  }
}

Programming Style

  • Use let instead of var.
  • Global constants and thread safety: Prefer const over let, especially in the global environment, where only constants should be set, not variables.
  • Use ESLint.
  • Use single quotes or backticks for static strings; avoid double quotes. Use backticks for dynamic strings.
  • Prefer destructuring assignment when assigning array members to variables.
  • Prefer destructuring assignment for function parameters that are object properties.
  • For functions returning multiple values, prefer object destructuring over array destructuring.
  • Single-line object definitions should not end with a comma. Multi-line object definitions should end with a comma.
  • Keep objects static; avoid adding new properties arbitrarily. If necessary, use Object.assign.
  • For dynamic property names, use property expressions when creating objects.
  • Use the spread operator (...) to copy arrays.
  • Write immediately invoked function expressions (IIFEs) as arrow functions: (() => { console.log('Welcome to the Internet.'); })();.
  • Use arrow functions for anonymous function parameters.
  • Replace Function.prototype.bind with arrow functions.
  • Avoid using the arguments variable in functions; use the rest operator (...) instead.
  • Set default function parameter values using default value syntax.
  • Use Object only for real-world entity objects; for key: value data structures, use Map, which has built-in traversal mechanisms.
  • Always use class instead of manipulating prototype directly, as it’s clearer and more concise.
  • Use Module syntax as the standard for JavaScript modules. Use import instead of require.
  • Use export instead of module.exports.

Asynchronous Iterator

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator
  .next()
  .then(iterResult1 => {
    console.log(iterResult1); // { value: 'a', done: false }
    return asyncIterator.next();
  })
  .then(iterResult2 => {
    console.log(iterResult2); // { value: 'b', done: false }
    return asyncIterator.next();
  })
  .then(iterResult3 => {
    console.log(iterResult3); // { value: undefined, done: true }
  });

for await…of

The for...of loop traverses synchronous Iterator interfaces, while for await...of is designed for asynchronous Iterator interfaces.

async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}

async function f() {
  for await (const data of req) body += data;
  const parsed = JSON.parse(body);
  console.log('got', parsed);
}

// for await...of can also traverse synchronous iterators
(async function () {
  for await (const x of ['a', 'b']) {
    console.log(x);
  }
})();

// Asynchronous iterator example
async function main(inputFilePath) {
  const readStream = fs.createReadStream(
    inputFilePath,
    { encoding: 'utf8', highWaterMark: 1024 }
  );
  for await (const chunk of readStream) {
    console.log('>>> ' + chunk);
  }
  console.log('### DONE ###');
}

Asynchronous Generator Functions

// Synchronous Generator function
function* map(iterable, func) {
  const iter = iterable[Symbol.iterator]();
  while (true) {
    const { value, done } = iter.next();
    if (done) break;
    yield func(value);
  }
}
// Asynchronous Generator function
async function* map(iterable, func) {
  const iter = iterable[Symbol.asyncIterator]();
  while (true) {
    const { value, done } = await iter.next();
    if (done) break;
    yield func(value);
  }
}

yield* Statement

async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}
async function* gen2() {
  // result will equal 2
  const result = yield* gen1();
}

(async function () {
  for await (const x of gen2()) {
    console.log(x);
  }
})();
// a
// b
Share your love