Categories
Modern JavaScript

Best of Modern JavaScript — Set Operations

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at sets.

Iterating Through Sets

We can iterate through sets with the for-of loop since sets are iterable objects.

For example, we can write:

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

Then we can write:

foo
bar
baz

logged.

The spread operator works with iterables to convert them to arrays.

This includes sets, so we can write:

const set = new Set(['foo', 'bar', 'baz']);
const arr = [...set];

and arr is [“foo”, “bar”, “baz”] .

Mapping and Filtering

To do map and filter operations, we can convert sets to arrays with the spread operator.

Then we can call the map and filter methods on those.

For example, we can map set entries to new values by writing:

const set = new Set([1, 2, 3]);
const squares = new Set([...set].map(x => x ** 2));

Then we get:

{1, 4, 9}

instead of:

{1, 2, 3}

To filter items, we can call the filter method on the array:

const set = new Set([1, 2, 3]);
const filtered = new Set([...set].filter(x => (x % 3) == 0));

We filtered out anything that isn’t evenly divisible by 3, so we get:

{3}

Union, Intersection, and Difference

We can compute union, intersection, and difference of 2 sets with various array operations.

Unions

A union is a set that has elements of both set a and b .

We can use the spread operator to create a union.

So we can write:

const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const union = new Set([...a, ...b]);

And union is {1, 2, 3, 5} .

The spread operator combines both sets into one array.

[...a, ...b] is equivalent to [...a].concat([...b]) .

The Set constructor converts that back into a set, and remove the duplicates in the process.

Intersection

A set intersection is a set that has elements in set a that’s also in set b .

To create an intersection from 2 sets, we can write:

const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const intersection = new Set(
  [...a].filter(x => b.has(x)));

We create 2 sets a and b .

Then we can get all the items in a that’s also in b with the filter method.

The has method checks if b also has the same item.

The Set constructor converts the array back to a set.

Therefore, we get:

{1, 3}

fot intersection .

Set Difference

The set difference lets us create a set from 2 sets a and b where the items in set a isn’t in set b .

To create the set difference, we can write:

const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const difference = new Set(
  [...a].filter(x => !b.has(x)));

We call filter with a callback that negates what’s returned in has to return all the items that aren’t in b but it’s in a .

So we get:

{2}

as the value of difference .

Conclusion

We can iterate through sets and computer the union, difference, and intersections with array operations.

Categories
Modern JavaScript

Best of Modern JavaScript — Set Operations

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at sets.

Iterating Through Sets

We can iterate through sets with the for-of loop since sets are iterable objects.

For example, we can write:

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

Then we can write:

foo
bar
baz

logged.

The spread operator works with iterables to convert them to arrays.

This includes sets, so we can write:

const set = new Set(['foo', 'bar', 'baz']);
const arr = [...set];

and arr is [“foo”, “bar”, “baz”] .

Mapping and Filtering

To do map and filter operations, we can convert sets to arrays with the spread operator.

Then we can call the map and filter methods on those.

For example, we can map set entries to new values by writing:

const set = new Set([1, 2, 3]);
const squares = new Set([...set].map(x => x ** 2));

Then we get:

{1, 4, 9}

instead of:

{1, 2, 3}

To filter items, we can call the filter method on the array:

const set = new Set([1, 2, 3]);
const filtered = new Set([...set].filter(x => (x % 3) == 0));

We filtered out anything that isn’t evenly divisible by 3, so we get:

{3}

Union, Intersection, and Difference

We can compute union, intersection, and difference of 2 sets with various array operations.

Unions

A union is a set that has elements of both set a and b .

We can use the spread operator to create a union.

So we can write:

const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const union = new Set([...a, ...b]);

And union is {1, 2, 3, 5} .

The spread operator combines both sets into one array.

[...a, ...b] is equivalent to [...a].concat([...b]) .

The Set constructor converts that back into a set, and remove the duplicates in the process.

Intersection

A set intersection is a set that has elements in set a that’s also in set b .

To create an intersection from 2 sets, we can write:

const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const intersection = new Set(
  [...a].filter(x => b.has(x)));

We create 2 sets a and b .

Then we can get all the items in a that’s also in b with the filter method.

The has method checks if b also has the same item.

The Set constructor converts the array back to a set.

Therefore, we get:

{1, 3}

fot intersection .

Set Difference

The set difference lets us create a set from 2 sets a and b where the items in set a isn’t in set b .

To create the set difference, we can write:

const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const difference = new Set(
  [...a].filter(x => !b.has(x)));

We call filter with a callback that negates what’s returned in has to return all the items that aren’t in b but it’s in a .

So we get:

{2}

as the value of difference .

Conclusion

We can iterate through sets and computer the union, difference, and intersections with array operations.

Categories
Modern JavaScript

Best of Modern JavaScript — New Data Structures

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at controlling the spreadability of arrays, maps, and sets.

Symbol.isConcatSpreadable

The Symbol.isConcatSpreadable isn’t part of any object in the ES6 standard library.

The mechanism exists purely for browser APIs and user code.

This means the subclasses of Array are spread by default.

Subclasses of Array can prevent instances from being spread by setting Symbol.isConcatSpreadable to fale .

This property can be an instance or a prototype property.

Other array-like objects are spread by concat if Symbol.isConcatSpreadable is true .

This means we can turn on spreading for some array-like objects.

Typed arrays aren’t spread and they don’t have the concat instance method.

Range of Array Indexes

ES6 follows the same rules as ES5 for the array index range.

Lengths are between 0 and 2 ** 32 — 1 .

And indexes are in the range between 0 and 2 ** 32 — 1 .

Strings and typed arrays have a larger range of indexes.

The upper bound of the range is because 2 ** 53 — 1 is the largest integer that JavaScript gloating point numbers can represent safely.

Maps and Sets

Maps and sets are new data structures introduced with ES6.

Maps can have arbitrary values. They’re stored as key-value pairs.

We can use an Array with entries having [key, value] pairs to set up the initial data.

For example, we can write:

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

We pass in an array of key-value pair arrays to the Map constructor to create our map.

A set is a collection of unique elements.

We can create a set with the Set constructor.

For example, we can write:

const arr = [2, 3, 5, 6, 6, 6, 2];
const unique = [...new Set(arr)];

then we get that unique is the array [2, 3, 5, 6] .

It’s useful for removing duplicate elements in an array.

Weak map is a map that doesn’t prevent its keys from being garbage collected.

This means that we won’t have to worry about memory leaks when using it.

Map

There’s no good way to create an object of key-value pairs with ES5 or earlier.

The only way is to create an object literal and use that as a map.

With ES6, we have the Map constructor.

To create a map, we can write:

const map = new Map();
map.set('foo', 'bar');

We create the map with the Map constructor.

Then we call the set method with the key and value as arguments to add or update the map with the entry.

If the entry with the key already exists, it’ll be overwritten.

The get method lets us get the item given the key.

So we can write:

const foo = map.get('foo');

and we get 'bar' .

The has method lets us check if an entry with the given key exists.

For example, we can write:

const hasFoo = map.has('foo');

then hasFoo would be true .

The delete method lets us delete the entry with the given key,

For example, we can write:

map.delete('foo')

to delete the entry with the key 'foo' .

Conclusion

We can control if an object if spreadable with the Array.prototype.concat method with the Symbol.isConcatSpreadable property.

Maps and sets are useful data structures that are introduced with ES6.

Categories
Modern JavaScript

Best of Modern JavaScript — Modules in Browsers and Import/Export Relationship

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 in browsers and the relationship between exports and imports.

ES6 Modules in Browsers

We can use ES6 modules in most modern browsers.

To use them, we just add a script tag with the type attribute set to module .

The file extension is still js like regular script files.

The content of the JavaScript file is delivered via the webserver.

ES6 modules in the browser combine to convenience by combining the syntax os synchronous loading with async loading underneath.

They’re less flexible than ES6 modules.

They can only be loaded at the top level so they can’t be conditional.

The restriction lets us analyze modules statically do see what modules are imported by other modules before execution.

Regular scripts can’t become modules because they’re synchronous.

We can’t import modules declaratively since they load one by one,

Therefore, a new type of script is added so we can load modules asynchronously.

To import a module in the browser, we can write:

<script type="module">
  import _ from "https://unpkg.com/lodash-es";

  console.log(_.uniq([1, 2, 2, 3]));
</script>

We import the ES6 module version of Lodash and used its methods.

The type attribute is set to module so we can import modules in our script.

Whether a JavaScript file is a module or a script is determined by where the code is written.

If we don’t have any exports or imports and use the file with a script tag without the type attribute set to module , then it’s a script.

Otherwise, it’s a module.

Imports’ Relationship with Exports

Imports’ relationship with exports different between module systems.

In CommonJS, imports are copies of exported value.

In ES6 modules, imports are read-only views of exports that updates as their values update.

For instance, if we have:

baz.js

module.exports = {
  add(x, y) {
    return x + y;
  },
  subtract(x, y) {
    return x - y;
  }
};

Then we can import it by writing:

const { add, subtract } = require("./baz");

const sum = add(1, 2);
const difference = subtract(1, 2);

A copy of the module is made when we require a CommonJS module.

This also applies when we import the whole module as one object.

So if we have:

const baz = require("./baz");

const sum = baz.add(1, 2);
const difference = baz.subtract(1, 2);

then baz is a copy of the baz.js module.

On the other hand, ES6 module imports and read-only views on the exported values.

Imports are connected live to the exported data.

If we import the whole module, then the whole imported module acts like a frozen object.

For example, if we have:

baz.js

export const add = (x, y) => {
  return x + y;
};

export const subtract = (x, y) => {
  return x - y;
};

foo.js

import { add, subtract } from "./baz";

const sum = add(1, 2);
const difference = subtract(1, 2);

add and subtract are read-only.

If we try to reassign an imported member to something, we’ll get an error.

We get the same result if we import the whole module:

import * as baz from "./baz";

const sum = baz.add(1, 2);
const difference = baz.subtract(1, 2);

We can’t assign a new value to baz .

Conclusion

ES6 module imports are read-only views on exports.

Also, we can use modules in the browser.

Categories
Modern JavaScript

Best of Modern JavaScript — Modules

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.

Exporting Function Expressions

The export functions expressions, we can put parentheses around our export statements.

For example, we can write:

export default (function () {});
export default (class {});

Classes are functions in JavaScript so the same rules apply.

Default Exporting Values Directly

We can default export values directly.

For example, we can write:

export default 'foo';

to export the string 'foo' .

Also, we can write:

const foo = function() {};

export { foo as default };

We create a function foo and export it with the as default keywords to make default exports.

We need this syntax so that we can turn variables declarations into default exports.

Imports and Exports Must be at the Top Level

Imports and exports must be at the top level.

For instance, we can write:

import 'foo';

But we can’t write:

if (true) {
  import 'foo';
}

or

{
  import 'foo';
}

They’ll both raise SyntaxErrors.

Imports are Hoisted

Imports aren’t hoisted, so we can use them before they’re defined.

For example, we can write:

console.log(add(1, 2));

import { add } from "./math";

And the return value of add will be logged.

Imports and Exports

Imports are read-only.

This enables the module system to allow cyclic dependencies.

Also, we can split code into multiple modules and it’ll still work as long as we don’t change the value of them.

Cyclic Dependencies

Cyclic dependencies are where 2 modules import members from each other.

They should be avoided since it makes both modules tightly coupled.

However, we may not be able to eliminate them altogether, so we’ve to live with them.

We can add cyclic dependencies in ES6 by wiring something like the following

For instance, we can write:

math.js

import { foo } from "./index";

export const bar = () => {
  foo();
};

index.js

import { bar } from "./math";

export const foo = () => {
  bar();
};

We import foo from index.js in math.js and use the imported function.

Likewise, we import bar from math.js and call that.

Other Importing Styles

In addition to named and default exports.

We can use import to just load the module and don’t import anything.

For example, we can write:

import 'lib';

Also, to rename imports, we can use the as keyword.

For example, we can write:

import { name as foo, bar } from "baz";

The as keyword is used to rename a named export name .

We can also use it to rename a default export.

For example, we can write:

import { default as foo } from "baz";

We can also use the as keyword by writing:

import * as baz from "baz";

to import the whole module and name it as baz .

Default imports can be mixed with named imports.

For example, we can write:

import foo, { bar, qux } from "baz";

Conclusion

We can export and import module members in various ways,

Cyclic dependencies also work with ES6’s module system.