Categories
Modern JavaScript

Best of Modern JavaScript — Child Classes

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at how to define classes with JavaScript.

Computed Method Names

We can add methods with computed property names.

For example, we can write:

class Foo {
  ['foo' + 'Bar']() {}
}

We pass an expression that returns a string or symbol into the square brackets to create a method with the given identifier.

We can also pass in a symbol by writing:

class Foo {
  [Symbol.iterator]() {
    //...
  }
}

Generator Methods

We can add generator methods to a class.

For example, we can write:

class Iterable {
  constructor(arr) {
    this.arr = arr;
  } 

  *[Symbol.iterator]() {
    for (const a of this.arr) {
      yield a;
    }
  }
}

We create an Iterable class with the constructor and a method with the Symbol.iterator method.

This makes our Iterable instance iterable.

The method is a generator as indicated by the * symbol.

And we use yield to return and pause each item.

We can then use it with a for-of loop by writing:

class Iterable {
  constructor(arr) {
    this.arr = arr;
  }

  *[Symbol.iterator]() {
    for (const a of this.arr) {
      yield a;
    }
  }
}

for (const x of new Iterable(['foo', 'bar', 'baz'])) {
  console.log(x);
}

And we get each entry of the array we passed in logged.

Subclassing

We can create subclasses from base classes.

The extends keyword lets us create subclasses.

For example, we can write:

class Person {
  constructor(name) {
    this.name = name;
  }
  toString() {
    return `(${this.name})`;
  }
}

class Employee extends Person {
  constructor(name, title) {
    super(name);
    this.title = title;
  }
  toString() {
    return `(${super.toString()}, ${this.title})`;
  }
}

We created the Employee class with the extends keyword to create a subclass of Person .

In the constructor of Employee , we call super to call the Person constructor from there.

This is required before we access this in the subclass.

We set the this.title property with the title parameter’s value.

In the toString method, we call super.toString to call toString of the Person instance.

If we create an instance of Employee , we can check the class that it’s created from.

For instance, we can write:

const emp = new Employee('jane', 'waitress');
console.log(emp instanceof Person);
console.log(emp instanceof Employee);

They both return true and as we expect.

Person is the base class and Employee is the child class.

The Prototype of a Subclass is the Superclass

The class syntax is just a convenient way to create constructors, so it sticks to the prototypical inheritance model.

The prototype of a subclass is the superclass.

If we have:

class Person {
  constructor(name) {
    this.name = name;
  }
  toString() {
    return `(${this.name})`;
  }
}

class Employee extends Person {
  constructor(name, title) {
    super(name);
    this.title = title;
  }
  toString() {
    return `(${super.toString()}, ${this.title})`;
  }
}

Then:

console.log(Object.getPrototypeOf(Employee) === Person)

logs true .

Static properties are inherited from the base class by the child class.

For example, if we have:

class Foo {
  static baz() {
    return 'baz';
  }
}

class Bar extends Foo {}

Then we can call:

Bar.baz()

We can also call static methods in child classes:

class Foo {
  static baz() {
    return 'baz';
  }
}

class Bar extends Foo {
  static baz() {
    return `child ${super.baz()}`;
  }
}

Conclusion

We can add computed method names, static members, and get parent class members from child classes.

Categories
Modern JavaScript

Best of Modern JavaScript — Class Checks and Instantiation

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at how to define classes with JavaScript.

Class Details

There are many details in classes.

The value we extend can be an arbitrary expression.

For example, we can extend a class that’s resulted from calling a function:

class Foo extends combine(Foo, Bar) {}

Checks

The JavaScript interpreter makes some checks when we create our classes.

A class name can’t be eval or arguments .

Duplicate class names aren’t allowed.

The name constructor can be used for a normal method and not getters, setters, or generator methods.

Classes can’t be called as a function.

If we do, a TypeError will be thrown.

Prototype methods can’t be used as constructors.

So we can’t write something like:

class Foo {
  bar() {}
}
new Foo.prototype.bar();

If we run that, we’ll get the ‘Uncaught TypeError: Foo.prototype.bar is not a constructor’ error.

Property Descriptors

Static properties of a class Bar are writable and configurable but not innumerable.

Bar.prototype isn’t writable, enumerable, or configurable.

Bar.prototype.constructor isn’t writable or enumerable, but it’s configurable.

Properties of Bar.prototype are writable and configurable, but it’s not enumerable.

This means we can update many properties dynamically.

Classes have Inner Names

A class has its own name property to return to its inner name.

Its name can also be used in the class itself.

For example, we can write:

class Foo {
  bar() {
    console.log(Foo.baz);
  }
}
Foo.baz = 1;

We referenced Foo to get the static property.

This is true even if we assign the class to a variable.

For instance, if we have:

const Bar = class Foo {
  bar() {
    console.log(Foo.baz);
  }
}
Bar.baz = 1;new Bar().bar();

Then the bar method call still logs 1.

This means we can refer it with the original name internally.

Subclassing

We create subclasses by using the extends keyword.

For static methods, they’re inherited directly by the subclass.

For instance, if we have:

class Person {
  static logName() {
    console.log(Person.name)
  }
}

class Employee extends Person {}
console.log(Person.logName === Employee.logName)

Then the console log will log true .

The prototype of a JavaScript class is Function.prototype .

This means that the JavaScript class is actually a function.

We can see this by writing:

class Person {
  static logName() {
    console.log(Person.name)
  }
}

console.log(Object.getPrototypeOf.call(Object, Person) === Function.prototype)

We call the Object.getPrototypeOf method to get the prototype of the Person class.

Then we checked that against the Function.prototype .

And this logs true .

So we know classes are functions underneath the hood.

Initializing Instances

Class instances are initialized by writing:

function Person(name) {
  this.name = name;
}

in ES5.

In ES6, the base constructor is created with the super method.

This triggers a parent constructor call.

In ES5, the super constructor is called when we use new to call the constructor.

It’s invoked with the function call.

Conclusion

When we create classes, JavaScript interpreter does many checks.

Also, static methods are inherited by subclasses, and classes are functions.

Categories
Modern JavaScript

Better JavaScript — Loops and Arrays

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

Don’t Modify an Object During Enumeration

We shouldn’t modify an object during enumeration.

The for-in loop isn’t required to keep current with object modifications.

So we may get items that are outdated in the loop.

This means that we should rely on the for-in loop to behave predictably if we change the object being modified.

So we shouldn’t have code like:

const dict = {
  james: 33,
  bob: 22,
  mary: 41
}

for (const name in dict) {
  delete dict.bob;
}

We have the dict object but we modified it within the for-in loop.

But the loop isn’t required to get the latest changes, so bob might still show up.

Prefer for-of Loops to for-in Loops for Array Iteration

The for-in loop isn’t meant to be used to iterate through arrays.

The order is unpredictable and we get the keys of the item instead of the item itself.

So if we have something like:

const scores = [4, 4, 5, 7, 7, 3, 6, 6];
let total = 0;
for (const score in scores) {
  total += score;
}
const mean = total / scores.length;

score would be the key of the array.

So we wouldn’t be adding up the scores.

Also, the key would be a string, so we would be concatenating the key strings instead of adding.

Instead, we should use the for-of loop to loop through an array.

For instance, we can write:

const scores = [4, 4, 5, 7, 7, 3, 6, 6];
let total = 0;
for (const score of scores) {
  total += score;
}
const mean = total / scores.length;

With the for-of loop, we get the entries of the array or any other iterable object so that we actually get the numbers.

Prefer Iteration Methods to Loops

We should use array methods for manipulating array entries instead of loops whenever possible.

For instance, we can use the map method to map entries to an array.

We can write:

const inputs = ['foo ', ' bar', 'baz ']
const trimmed = inputs.map((s) => {
  return s.trim();
});

We called map with a callback to trim the whitespace from each string.

This is much shorter than using a loop like:

const inputs = ['foo ', ' bar', 'baz ']
const trimmed = [];
for (const s of inputs) {
  trimmed.push(s.trim());
}

We have to write more lines of code to do the trimming and push it to the trimmed array.

There’re many other methods like filter , reduce , reduceRight , some , every , etc. that we can use to simplify our code.

Conclusion

We shouldn’t modify objects during enumeration.

Also, we should prefer iteration methods to loops.

The for-of is better than the for-in loop for iteration.

Categories
Modern JavaScript

Best of Modern JavaScript — Module Details

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the design of the ES6 module system.

Use a Variable to Specify from which Module I want to Import

We can specify which module to import with the import function.

It takes a string with the path to the module.

For instance, we can write:

(async () => {
  const foo = await import("./foo");
  //...
})();

to import a module with the import function.

It takes a string so we can pass in a string generated dynamically.

It returns a promise so we use await to get the resolved value.

Import a Module Conditionally or On-demand

With the import function, we can import a function conditionally or on-demand.

For instance, we can write:

(async () => {
  if (Math.random() < 0.5) {
    const foo = await import("./foo");
    //...
  }
})();

to import a module conditionally.

Using Variables with Import Statements

We can’t use variables with our import statements.

So we can’t write something like:

import foo from 'bar-' + SUFFIX;

But with the import function, we can write:

(async () => {
  if (Math.random() < 0.5) {
    const foo = await import(`bar-${SUFFIX}`);
    //...
  }
})();

Use Destructuring in an import Statement

We can’t use nested destructuring in an import statement.

This makes sense because exports can only be done at the top level.

It looks like destructuring but the syntax is different.

Imports are static and views on exports.

So we can’t write something like:

import { foo: { bar } } from 'some_module';

Named Exports

With named exports, we can enforce a static structure with objects.

If we create a default export with an object, then we can’t statically analyze the object.

The object can have any properties and they can be nested.

eval() the Code of a Module

We can’t call eval on module code.

Modules are too high level for eval .

eval accepts scripts which doesn’t allow the import or export keywords.

Benefits of ES6 Modules

ES6 modules come with several benefits.

They include a more compact syntax.

Static module structure also helps with eliminating dead code, static checks, optimizations, etc.

Also, we check for cyclic dependencies.

With a standard module system, we eliminate the fragmentation of multiple module systems.

Everything using old module systems will migrate to ES6 standard modules.

Also, we don’t have to use objects as namespaces anymore.

This functionality is now provided by modules.

Objects like Math and JSON serve as namespaces for segregating entities.

Conclusion

ES6 modules provide us with many benefits over older non-standard module systems.

Also, they can be dynamically imported.

They allow for various optimizations.

Categories
Modern JavaScript

Best of Modern JavaScript — __proto__ and Descriptors

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at new OOP features in JavaScript.

The proto Property

We can use the __proto__ property to get and set the prototype of an object easily.

For example, we can write:

Object.getPrototypeOf({ __proto__: null })

Then it’ll return null since we set the __proto__ property to null .

This doesn’t work with the string version of the key as it creates its own property:

Object.getPrototypeOf({ ['__proto__']: null })

It’ll return Object.prototype .

And Object.keys({ [‘__proto__’]: null }) returns the array[ ‘__proto__’ ] .

Also, if we define our own property named __proto__ , it also won’t work.

For instance, we can write:

const obj = {};
Object.defineProperty(obj, '__proto__', { value: 'bar' })

Objects that don’t have Object.prototype as a Prototype

We can use the Object.create method with the argument null to create an object with no prototype.

For example, we can write:

const obj = Object.create(null);

Then obj has no __proto__ property.

This method makes it easy to create dictionary objects where we don’t want any inherited properties from it.

proto and JSON

__proto__ may be stringified with JSON.stringify .

For example, we can write:

JSON.stringify({['__proto__']: 'foo' })

And then that returns:

"{"__proto__":"foo"}"

Detecting support for ES6-style proto

There are several ways to detect the ES6 style __proto__ property.

We can check it with the Object.prototype.__proto__ property and the __proto__ in the object literals.

For example, we can write:

const supported = ({}).hasOwnProperty.call(Object.prototype, '__proto__');

We check if the Object.prototype.__proto__ property exists.

We can also check that getter and setter of __proto__ are functions to make sure that we can get and set the value of this property.

For example, we can write:

const desc = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
const supported = (
  typeof desc.get === 'function' && typeof desc.set === 'function'
);

And we can also use the Object.getPrototypeOf method to do the check by passing in an object with the __proto__ property set to null .

Then we can use that to check if the prototype returned is null .

For example, we can write:

const supported = Object.getPrototypeOf({__proto__: null}) === null;

Enumerability in ES6

In JavaScript, each object can have zero or more p[roperties.

Each property has a key and 3 or more descriptors.

All properties have the enumerable and configurable attributes

enumerable means that whether we show the property everywhere.

configurable set to false disables property value changes and prevents deletion.

Properties have the value and writable attributes.

value has the value of the property.

And writable controls whether we can change the value of the property.

Properties also have the getter get accessor and setter set accessor.

We can use the Object.getOwnPropertyDescriptor method to get the property descriptors.

For example, we can write:

const obj = { foo: 'bar' };
consr desc = Object.getOwnPropertyDescriptor(obj, 'foo')

Then desc is:

{
  value: "bar",
  writable: true,
  enumerable: true,
  configurable: true
}

Conclusion

Object properties have property descriptors.

Also, the __proto__ property acts differently in different contexts.

We can check if the property is available in different ways.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.