Categories
Modern JavaScript

Best of Modern JavaScript — Import and Export Styles

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 use JavaScript modules.

Named Exporting Styles

We can export anything in our JavaScript modules.

Some things we can export include:

export var foo = "...";
export let bar = "...";
export const MY_CONST = "...";

export function qux() {
  //...
}

export function* gen() {
  //...
}

export class Baz {
  //...
}

We export var , let , const variables.

And we can also export various kinds of functions and classes.

We can also list all the exports at the end of the file.

For example, we can write:

var foo = "...";
let bar = "...";
const MY_CONST = "...";

function qux() {
  //...
}

function* gen() {
  //...
}

class Baz {
  //...
}

export { MY_CONST, foo, bar, qux, gen, Baz };

We have one export statement with all the items we export at the end.

Re-exporting

We can re-export module members by writing:

export { foo, bar } from "./foo";

Given that foo.js has:

export var foo = "...";
export let bar = "...";

This syntax is the shorthand for importing all the members from foo.js and export them with the same names all in one line.

We can also use the as keyword to export the members.

For example, we can write;

export { foo as qux, bar } from "./foo";

to rename the foo variable to qux .

We can do the same thing as default export.

For example, we can write:

export { default } from "./foo";

given that we have:

export default "...";

We can also re-export named exports as default exports.

For example, we can write:

export { baz as default } from "./foo";

given that we have:

export const baz = "...";

in foo.js .

We imported baz from foo.js , which is a named export as a default export.

Having Both Named Exports and a Default Export in a Module

We can have both named and default exports inside a module.

For example, we can write:

var foo = "...";
let bar = "...";
const MY_CONST = "...";

function qux() {
  //...
}

function* gen() {
  //...
}

class Baz {
  //...
}

export { MY_CONST, foo, bar, qux, gen, Baz };

export default function quux() {}

all in one module.

Then we can write:

import quux, { MY_CONST, foo, bar, qux, gen, Baz } from "./foo";

to import them.

This is similar to what we had with CommonJS.

It’s probably a good idea to avoid mixing named and default exports to avoid confusion.

The default export is just another named export.

However, its name is default unless we rename it with as or import it with the name we want.

For example, we can import default exports by writing:

import { default as foo } from 'lib';
import foo from 'lib';

We can use default as an export name, but not as a variable name.

Conclusion

There’re many ways to export something with our code.

We can also re-export imports all in one line.

Categories
Modern JavaScript

Best of Modern JavaScript — for-of

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 for-of loop.

The for-of Loop

The for-of loop lets us replace the for-in loop and forEach with one loop.

It can also be used to iterate over any iterable object.

For example, we can use the for-of loop to loop through an array by writing:

const arr = [1, 2, 3];
for (const x of arr) {
  console.log(x);
}

We have an arr array and we loop through it with the for-of loop.

x is the array entry we’re iterating through.

break and continue also work inside for-of loops.

For example, we can write:

const arr = [1, 2, 3];
for (const x of arr) {
  if (x === 2) {
    break;
  }
  console.log(x);
}

or:

const arr = [1, 2, 3];
for (const x of arr) {
  if (x === 2) {
    continue;
  }
  console.log(x);
}

We can still use break to stop the loop.

And we can use continue to skip to the next iteration.

Also, it works with iterable object entry destructuring.

For example, we can write:

const arr = [1, 2, 3];
for (const [index, element] of arr.entries()) {
  console.log(index, element);
}

We call the arr.entries() method which returns an array with arrays of the index and the element for the index as the entries.

The for-of loop also works with other kinds of iterable objects.

For example, we can use it to iterate through maps.

We can write:

const map = new Map([
  [1, 'foo'],
  [2, 'bar'],
]);

for (const [key, value] of map) {
  console.log(key, value);
}

We create the Map instance and loop through it with the for-of loop.

We destructure each entry with the array with key and value .

And run console.log method to log the item.

The for-of loop only works with iterable values.

This means that we need to convert plain objects to iterable objects to make it work with the for-of loop.

For example, if we have an object with the length property and the indexes as keys, we’ve to convert it to an array with Array.from .

This means if we write:

const arrayLike = {
  length: 2,
  0: 'foo',
  1: 'bar'
};

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

We’ll get the ‘Uncaught TypeError: arrayLike is not iterable’ error.

To fix this, we’ve to use the Array.from method:

const arrayLike = {
  length: 2,
  0: 'foo',
  1: 'bar'
};

for (const x of Array.from(arrayLike)) {
  console.log(x);
}

Array.from converts the object to an array so we can use the returned array with the for-of loop.

const vs var in Iterable Variables

Since for-of loop creates a fresh binding in each iteration, we can use const to declare our loop variable in the loop head.

This works as long as we don’t reassign the value in the loop body.

For instance, we can write:

const arr = [];
const orig = ['foo', 'bar', 'baz'];
for (const elem of orig) {
  arr.push(() => elem);
}
console.log(arr.map(f => f()));

The console log gives us [“foo”, “bar”, “baz”] since we const variables are confined within the block.

let works the same way as const , but we can reassign it to a value in the loop block.

On the other hand, if we use var , we’ll get different results.

For instance, if we have:

const arr = [];
const orig = ['foo', 'bar', 'baz'];
for (var elem of orig) {
  arr.push(() => elem);
}
console.log(arr.map(f => f()));

Then we get [“baz”, “baz”, “baz”] This is because a var variable is also available outside the block.

So when we run the functions we pushed to arr , elem will be 'baz' .

Therefore, we eliminate confusion by not using var .

Conclusion

for-of loop provides us with many conveniences with iteration.

Categories
Modern JavaScript

Best of Modern JavaScript — Find Item and Holes

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at some instance methods of Array and holes.

Array.prototype.findIndex

The findIndex method lets us return the index of the item that’s found.

It takes a callback that returns the condition that we’re looking for.

The 2nd argument is the value of this we use inside the callback.

It returns the index of the first element that meets the given condition.

If none is found, then -1 is returned.

For example, we can write:

const index = [2, -1, 6].findIndex(x => x < 0)

Then index is 1.

And if we write:

const index = [2, 1, 6].findIndex(x => x < 0)

Then index is -1.

The signature is the callback is predicate(element, index, array) .

element is the array being iterated through.

index is the index of the array.

array is the array it’s called on.

Finding NaN via findIndex()

With findIndex , we can find NaN because we can use Object.is to compare against NaN .

For example, we can write:

const index = [2, NaN, 6].findIndex(x => Object.is(x, NaN))

Object.is assumes that NaN is the same as itself, so we can use it to check for NaN .

This doesn’t work with indexOf .

Array.prototype.copyWithin()

The Array.prototype.copyWithin() method lets us copy a chunk of an array into another location.

The signature of it is Array.prototype.copyWithin(target: number, start: number, end = this.length) .

target is the starting index to copy to.

start is the starting index of the chunk to copy from.

And end is the ending index of the chunk to copy from.

So if we write:

const arr = [1, 2, 3, 4, 5, 6];
arr.copyWithin(2, 0, 2);

Then we get:

[1, 2, 1, 2, 5, 6]

as the new value of arr .

Array.prototype.fill()

Array.prototype.fill() is a method that lets us fill an array with the given value.

Its signature is:

Array.prototype.fill(value, start=0, end=this.length)

The value is the value to populate.

start is the starting index of the array filling.

end is the ending index to fill to the array.

For example, we can write:

const arr = ['foo', 'bar', 'baz', 'qux'];
arr.fill(7, 1, 3)

Then arr is [“foo”, 7, 7, “qux”] .

Holes in Arrays

JavaScript allows holes in arrays.

Indexes that have no associated element inside the array is a hole.

For example, we can write:

const arr = ['foo', , 'bar']

to add an array with a hole in it.

ES6 treats holes in undefined or null elements.

If we call:

const index = [, 'foo'].findIndex(x => x === undefined);

The index is 0.

And if we write:

const entries = ['foo', , 'bar'].entries();

Then entries is:

[
  [
    0,
    "foo"
  ],
  [
    1,
    null
  ],
  [
    2,
    "bar"
  ]
]

There’re some inconsistencies in how they’re treated.

With the in operator:

const arr = ['foo', , 'bar'];
console.log(1 in arr);

We get false logged with our arr .

Conclusion

Holes in arrays are allowed in JavaScript.

Also, there’re various methods to find items with arrays.

Categories
Modern JavaScript

Best of Modern JavaScript — Class Safety and Pros and Cons

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.

Safety Checks

The JavaScript interpreter does some safety checks when instantiating classes.

this is originally uninitialized in the derived constructor means that an error will be thrown if we try to access this before calling super in a subclass.

When this is initialized, then calling super produces a ReferenceError since super is already called to initialize this .

If a constructor returns an non-object, then a TypeError is thrown.

If a constructor returns an object explicitly, then that’s used as the result.

In this case, it doesn’t matter if this is initialized or not.

The extends Keyword

The value that we’re extending must be a constructor.

However, null is allowed.

For instance, we can write:

class Foo extends Bar {}

given that Bar is a constructor.

Foo.prototype would be Bar in this case.

We can also write:

class Foo extends Object {}

since Object is a constructor.

Foo.prototype would be Object in this case.

And we can also write:

class Foo extends null {}

Then Foo.prototype is null .

Referring to Base Class Properties in Methods

We can refer to base class properties in methods.

For example, if we have:

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

  toString() {
    return `${this.name}`;
  }
}

Then we can create a subclass for Person by writing:

class Student extends Person {
  constructor(name, grade) {
    super(name);
    this.grade = grade;
  }

  toString() {
    return `${super.toString()} (${this.grade})`;
  }
}

We create the toString to create a method that calls the toString method of Person with super.toString .

This is done by searching for the toString method up the prototype chain to get the toString method and calls it.

Then the method is called if it’s found.

This is different from what we did in ES5 or earlier.

In earlier versions, we call a superclass method with the call method.

For example, we can write:

var result = Person.prototype.toString.call(this);

With ES6 or later, as we can see, we don’t have to refer to the parent class directly.

We just use super .

super can be used in subclass methods and constructors.

They can’t be used in function declarations.

A method that uses super can’t be moved.

It’s tied to the object that it’s created in.

Pros and Cons of Classes

There are some pros and cons of classes.

The class syntax makes constructors look more like classes from class-based languages.

The unconventional pattern of inheritance throws many people off.

It hides a lot of complexity with managing prototypes and constructors.

Classes are backward compatible with any current code, so no breaking change is introduced.

Subclassing is supported by the class syntax.

It’s also easier for beginners to under the class syntax instead of prototypes.

No library is required for an inheritance, which is good.

This makes them more portable.

They also provide foundations for more advanced OOP features like traits and mixins.

Classes can also be statically analyzed more easily with IDEs, text editors, and more.

However, they do hide the true nature of JavaScript’s object-oriented model.

JavaScript classes look like its own entity, but it’s actually a function.

However, because of the need for backward compatibility, JavaScrtipt classes can’t be a completely new entity.

This is a compromise to make the class syntax work with existing code.

Conclusion

The JavaScript interpreter provides us with safety checks for classes.

Also, there are pros and cons with the class syntax.

Categories
Modern JavaScript

Best of Modern JavaScript — Catches for Classes and Module Basics

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.

Single Inheritance

We can only inherit from one class with the extends keyword.

However, we can generate a new class from existing classes and inherit from that.

This works since extends accepts an expression that returns a constructor.

Classes Lock-In

If we want to instantiate a class, we’re forced to use the new keyword with ES6.

This means switching from a class to a factory function will mean we’ve to remove the new keyword from the existing code.

However, we can override what the constructor returns by return our own object with the constructor .

The module system and class syntax also make refactoring JavaScript code much easier than before.

Classes can’t be called as a Function

Classes can’t be called as a function even though they’re functions underneath.

This keeps options open for the future to add ways to handle function calls with classes.

Instantiate a Class Given an Array of Arguments

We can let our class constructor take an array of arguments with the rest syntax.

For instance, we can write:

class Foo {
  constructor(...args) {
    //...
  }
}

Then we can instantiate it by running:

new Foo(...args);

where args is an array of arguments.

We use the spread operator to spread the arguments into the args array as arguments.

We can then use them however we like.

Also, we can use the Reflect.construct method to create a class instance with an array of arguments.

For instance, we can write:

`const foo = Reflect.construct(Foo, ['foo', 'bar']);

We pass in our class or constructor as the first argument, and we pass in an array of arguments for the constructor as the 2nd argument.

Modules

JavaScript doesn’t have a native module system until ES6.

However, there were many module systems implemented as libraries.

ES6 modules can ve accessed in the browser and Node.js.

In the browser, we add a script tag with the type attribute set to module to import a module.

Modules are in strict mode by default.

Top-level value os this is local to the module.

Modules are executed asynchronously.

The import keyword is also provided to import module items.

Programmatic imports are also available.

The import function returns a promise that resolves to an object with the module’s contents.

The file extension for modules is still .js .

This is different from old-style scripts.

Scripts are run synchronously unless specified otherwise.

And they default to non-strict mode.

However, they can be imported asynchronously.

Each module is a piece of code that’s run once it’s loaded.

In a module, there may be declarations of various kinds, like functions, classes, objects, etc.

A module can also import things from other modules.

They may be imported with a relative path like './foo/bar' or an absolute path like '/foo/bar' .

Modules are singletons so all imports of a module are the same.

Conclusion

Classes can’t be called as a function.

We can instantiate them with an array of arguments.

Modules are useful for dividing code into smaller chunks.