Lesson 08-Basic Extensions of ES6~ES10 Data Types

let and const Commands

// 1. Variables declared with `let` are only valid within the block they are defined in, preventing global pollution like `var`. `let` introduces block-level scoping.
// 2. No variable hoisting.
// 3. Duplicate declarations are not allowed.
let a = 10;
// `const` declares a read-only constant. Once declared, its value cannot be changed.
const PI = 3.1415;

JavaScript has a top-level object providing the global environment (global scope) where all code runs. However, this top-level object varies across implementations:

  • In browsers, the top-level object is window, but Node and Web Workers do not have window.
  • In browsers and Web Workers, self points to the top-level object, but Node does not have self.
  • In Node, the top-level object is global, but other environments do not support it.

To access the top-level object across environments, this is commonly used, but it has limitations:

  • In the global environment, this returns the top-level object. In Node modules and ES6 modules, this returns the current module.
  • Inside functions, if the function is not run as an object method but as a standalone function, this points to the top-level object. In strict mode, this is undefined.
  • In both strict and non-strict modes, new Function('return this')() always returns the global object. However, if the browser uses CSP (Content Security Policy), methods like eval and new Function may be unavailable.
// Accessing the top-level object
// Method 1
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);

// Method 2
var getGlobal = function () {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('unable to locate global object');
};

Variable Destructuring Assignment

Array Destructuring

let [a, b, c] = [1, 2, 3]; // If destructuring fails, the variable's value is `undefined`.
let [a, [b], d] = [1, [2, 3], 4];
let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2

Object Destructuring

let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // If destructuring fails, the variable's value is `undefined`.
let { log, sin, cos } = Math;
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};
let { loc, loc: { start }, loc: { start: { line }} } = node;

String Destructuring

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5

Numeric and Boolean Destructuring

let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

Function Parameter Destructuring

function add([x, y]) {
  return x + y;
}
add([1, 2]); // 3

[[1, 2], [3, 4]].map(([a, b]) => a + b);

Use Cases

// Swap variable values
let x = 1;
let y = 2;
[x, y] = [y, x];

// Return an array
function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// Return an object
function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

// Ordered parameters
function f([x, y, z]) { ... }
f([1, 2, 3]);

// Unordered parameters
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

// Extract JSON data
let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]

// Iterate over Map structure
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
  console.log(key + " is " + value);
}

// Import specific module methods
const { SourceMapConsumer, SourceNode } = require("source-map");

String Extensions

Template literals allow embedding variables within ${}:

// Regular string
`In JavaScript '\n' is a line-feed.`
// Multiline string
`In JavaScript this is
 not legal.`
console.log(`string text line 1
string text line 2`);
// Embed variables
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Nested function in template literal
function fn() {
  return "Hello World";
}
`foo ${fn()} bar`

New String Methods

  • String.fromCodePoint(): Returns a character from a Unicode code point, but cannot handle code points greater than 0xFFFF.
  • String.raw(): Returns a string with escaped slashes (adds a slash before each slash), often used for template literal processing.
  • String.codePointAt(): Returns the code point of a character.
  • String.normalize(): Unifies different representations of a character into a standard form (Unicode normalization).
  • String.includes(): Returns a boolean indicating whether the parameter string is found.
  • String.startsWith(): Returns a boolean indicating whether the parameter string is at the start.
  • String.endsWith(): Returns a boolean indicating whether the parameter string is at the end.
  • String.repeat(): Returns a new string repeating the original string n times.
  • String.padStart(): Pads the start of a string; first parameter is the maximum length, second is the padding string.
  • String.padEnd(): Pads the end of a string; first parameter is the maximum length, second is the padding string.
  • String.trimStart(): Removes leading spaces.
  • String.trimEnd(): Removes trailing spaces.
  • String.matchAll(): Returns all matches of a regular expression in the string.

Regular Expression Extensions

RegExp Constructor

If the first argument to the RegExp constructor is a regular expression object, the second argument can specify modifiers. The returned expression ignores the original modifiers, using only the new ones.

new RegExp(/abc/ig, 'i').flags;

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true

// Match all numbers
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true

// Match all spaces
\p{White_Space}
// Match all letters in various scripts, equivalent to Unicode \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// Match all non-letters in various scripts, equivalent to Unicode \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// Match Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu
// Match all arrow characters
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true

Named Capture Groups

ES2018 introduced named capture groups, allowing each group to have a specified name.

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');

Regular Expression Match Indices

const text = 'zabbcdef';
const re = /ab/;
const result = re.exec(text);
result.index // 1
result.indices // [ [1, 3] ]

const text = 'zabbcdef';
const re = /ab+(?<Z>cd)/;
const result = re.exec(text);
result.indices.groups // { Z: [ 4, 6 ] }

String.prototype.matchAll()

ES2020 introduced String.prototype.matchAll(), which retrieves all matches at once, returning an iterator rather than an array.

const string = 'test1test2test3';
// The `g` modifier is optional
const regex = /t(e)(st(\d?))/g;
for (const match of string.matchAll(regex)) {
  console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

// Convert to array (Method 1)
[...string.matchAll(regex)]
// Convert to array (Method 2)
Array.from(string.matchAll(regex))

String Regular Expression Methods

Strings have four methods that use regular expressions: match(), replace(), search(), and split(). ES6 ensures these methods internally call RegExp instance methods, centralizing all regex-related functionality on the RegExp object:

  • String.prototype.match calls RegExp.prototype[Symbol.match]
  • String.prototype.replace calls RegExp.prototype[Symbol.replace]
  • String.prototype.search calls RegExp.prototype[Symbol.search]
  • String.prototype.split calls RegExp.prototype[Symbol.split]

RegExp.prototype.unicode Property

The unicode property on regex instances indicates whether the u modifier is set.

const r1 = /hello/;
const r2 = /hello/u;
r1.unicode // false
r2.unicode // true

RegExp.prototype.sticky Property

The sticky property indicates whether the y modifier is set.

var r = /hello\d/y;
r.sticky // true

RegExp.prototype.flags Property

The flags property returns the modifiers of a regular expression.

// ES5 source property returns the regex pattern
/abc/ig.source
// "abc"
// ES6 flags property returns the regex modifiers
/abc/ig.flags
// 'gi'

Numeric Extensions

Binary and Octal Notation

ES6 introduced new notations for binary (0b or 0B) and octal (0o or 0O) numbers.

0b111110111 === 503 // true
0o767 === 503 // true

Number.isFinite(), Number.isNaN()

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
isFinite(25) // true
isFinite("25") // true
Number.isFinite(25) // true
Number.isFinite("25") // false

Number.parseInt(), Number.parseFloat()

// ES5
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
// ES6
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

Number.isInteger()

Number.isInteger(25) // true
Number.isInteger(25.1) // false
Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false
Number.isInteger(5E-324) // false
Number.isInteger(5E-325) // true

Number.EPSILON

Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"

Safe Integers and Number.isSafeInteger()

Number.isSafeInteger() checks if an integer is within the safe range.

Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
Number.isSafeInteger(Infinity) // false
Number.isSafeInteger(-Infinity) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

Math Object Extensions

// Math.trunc removes the decimal part, returning the integer part.
Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4

// Math.sign determines if a number is positive, negative, or zero.
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0

// Math.cbrt computes the cube root.
Math.cbrt(-1) // -1
Math.cbrt(0)  // 0
Math.cbrt(1)  // 1

// Math.clz32 converts to a 32-bit unsigned integer and counts leading zeros.
Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22

// Math.imul multiplies two numbers as 32-bit signed integers.
Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4

// Math.fround returns a number in 32-bit single-precision float format.
Math.fround(0)   // 0
Math.fround(1)   // 1
Math.fround(2 ** 24 - 1)   // 16777215

// ES6 added four logarithmic methods
Math.expm1()
Math.log1p()
Math.log10()
Math.log2()

// Hyperbolic functions
Math.sinh(x)
Math.cosh(x)
Math.tanh(x)
Math.asinh(x)
Math.acosh(x)
Math.atanh(x)

Exponentiation Operator

ES2016 introduced the exponentiation operator (**).

2 ** 2 // 4
2 ** 3 // 8

BigInt Data Type

ES2020 introduced BigInt to handle large integers with unlimited precision.

const a = 2172141653n;
const b = 15346349309n;
// BigInt maintains precision
a * b // 33334444555566667777n
// Regular integers lose precision
Number(a) * Number(b) // 33334444555566670000

BigInt values must have an n suffix.

1234 // Regular integer
1234n // BigInt
// BigInt operations
1n + 2n // 3n
0b1101n // Binary
0o777n // Octal
0xFFn // Hexadecimal

The BigInt constructor converts values to BigInt, similar to Number().

BigInt(123) // 123n
BigInt('123') // 123n
BigInt(false) // 0n
BigInt(true) // 1n

const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max)
// 9223372036854775807n
BigInt.asIntN(64, max + 1n)
// -9223372036854775808n
BigInt.asUintN(64, max + 1n)
// 9223372036854775808n

Boolean(0n) // false
Boolean(1n) // true
Number(1n)  // 1
String(1n)  // "1"

Function Extensions

Default Parameter Values

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}
let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

Rest Parameters

ES6 introduced rest parameters (...variableName) to capture excess arguments as an array, eliminating the need for arguments.

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10

Strict Mode

ES6 prohibits explicitly enabling strict mode in functions with default values, destructuring, or spread operators.

// Error
function doSomething(a, b = a) {
  'use strict';
  // code
}
// Error
const doSomething = function ({a, b}) {
  'use strict';
  // code
};
// Error
const doSomething = (...a) => {
  'use strict';
  // code
};
const obj = {
  // Error
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

name Property

The name property in ES6 returns the actual function name.

var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"

Arrow Functions

var f = () => 5;
// Equivalent to
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// Equivalent to
var sum = function(num1, num2) {
  return num1 + num2;
};
var sum = (num1, num2) => { return num1 + num2; }

Tail Call Optimization

A tail call occurs when a function’s last action is calling another function.

function f(x) {
  return g(x);
}

Trailing Comma in Parameters

ES2017 allows a trailing comma in the last function parameter.

function clownsEverywhere(
  param1,
  param2,
) { /* ... */ }
clownsEverywhere(
  'foo',
  'bar',
);

Function.prototype.toString()

The toString() method now returns the exact function code, including comments and spaces.

function /* foo comment */ foo () {}
foo.toString()
// function /* foo comment */ foo () {}

Optional catch Parameter

try {
  // ...
} catch {
  // ...
}

Array Extensions

Spread Operator

The spread operator (...) is the inverse of rest parameters, converting an array into a comma-separated sequence.

function push(array, ...items) {
  array.push(...items);
}
function add(x, y) {
  return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42

function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

Array.from()

Array.from() converts array-like objects and iterables (e.g., Set, Map) into true arrays.

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

Array.from([1, 2, 3])
// [1, 2, 3]

Array.of()

Array.of() converts a set of values into an array.

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

Array.prototype.copyWithin()

Array.prototype.copyWithin(target, start = 0, end = this.length)
// Copy position 3 to position 0
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
// -2 corresponds to position 3, -1 to position 4
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

Array.prototype.find() and findIndex()

find() returns the first array element meeting the condition. findIndex() returns the index of the first matching element or -1 if none match.

[1, 4, -5, 10].find((n) => n < 0)
// -5

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

Array.prototype.fill()

fill() populates an array with a specified value.

['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]

let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

Array.prototype.entries(), keys(), and values()

ES6 introduced entries(), keys(), and values() for array iteration, each returning an iterator.

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

Array.prototype.includes()

includes() returns a boolean indicating whether an array contains a given value.

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

Array.prototype.flat() and flatMap()

flat() flattens nested arrays into a single-level array. flatMap() maps each element and flattens the result.

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

// Equivalent to [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

Object Extensions

const proto = {
  foo: 'hello'
};
const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

Object Destructuring

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
};

Optional Chaining Operator

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined

Optional chaining has three uses:

  • obj?.prop // Object property
  • obj?.[expr] // Same as above
  • func?.(...args) // Function or object method call

New Object Methods

Object.is()

Compares two values for strict equality, similar to ===.

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

Object.assign()

Merges source objects’ enumerable properties into a target object.

const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

const obj = {a: 1};
Object.assign(obj) === obj // true
// Object.assign performs shallow copying, not deep copying.
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2

Common Use Cases

// Add properties to an object
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

// Add methods to an object
Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    // ...
  },
  anotherMethod() {
    // ...
  }
});

// Clone an object
function clone(origin) {
  return Object.assign({}, origin);
}

// Merge multiple objects
const merge =
  (target, ...sources) => Object.assign(target, ...sources);

// Set default values for properties
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};
function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

Object.getOwnPropertyDescriptors()

Returns the descriptor objects for all own (non-inherited) properties of an object.

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)

const source = {
  set foo(value) {
    console.log(value);
  }
};
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')

proto, Object.setPrototypeOf(), Object.getPrototypeOf()

__proto__ reads or sets an object’s prototype. Object.setPrototypeOf() sets the prototype, returning the object itself. Object.getPrototypeOf() reads the prototype.

// ES5
const obj = {
  method: function() { ... }
};
obj.__proto__ = someOtherObj;
// ES6
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

// Syntax
Object.setPrototypeOf(object, prototype)
// Usage
const o = Object.setPrototypeOf({}, null);

function Rectangle() {
  // ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

Object.keys(), Object.values(), Object.entries()

Iterate over objects.

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

Object.fromEntries()

The inverse of Object.entries(), converts an array of key-value pairs into an object.

// Example 1
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }
// Example 2
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }

Symbol

Represents a unique value, the seventh data type in JavaScript.

let mySymbol = Symbol();
// Method 1
let a = {};
a[mySymbol] = 'Hello!';
// Method 2
let a = {
  [mySymbol]: 'Hello!'
};
// Method 3
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// All methods yield the same result
a[mySymbol] // "Hello!"

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

Set and Map Data Structures

Set

const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i);
}
// 2 3 5 4

// Example 1
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// Example 2
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// Example 3
const set = new Set(document.querySelectorAll('div'));
set.size // 56

// Remove duplicates from an array
[...new Set(array)]

// Remove duplicates from a string
[...new Set('ababbc')].join('')
// "abc"

WeakSet

WeakSet is similar to Set, storing unique values, but its members must be objects, and it holds weak references, meaning the garbage collector ignores WeakSet references.

WeakSet cannot be iterated because its members are weak references that may disappear at any time. It is useful for storing DOM nodes without risking memory leaks when nodes are removed.

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo);    // false
ws.delete(window);
ws.has(window);    // false

Map

Map is a collection of key-value pairs, where keys can be any type, not just strings, offering a more robust hash structure than objects.

const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false

Map provides three iterator-generating methods and one traversal method:

  • Map.prototype.keys(): Returns an iterator for keys.
  • Map.prototype.values(): Returns an iterator for values.
  • Map.prototype.entries(): Returns an iterator for all entries.
  • Map.prototype.forEach(): Iterates over all members.

Conversions with Other Data Structures

// Map to array
const myMap = new Map()
  .set(true, 7)
  .set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

// Array to Map
new Map([
  [true, 7],
  [{foo: 3}, ['abc']]
])

// Map to object
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}
const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }

// Object to Map
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));

// Map to JSON
function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

// JSON to Map
function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

WeakMap

WeakMap is similar to Map but only accepts objects (except null) as keys, holding weak references.

WeakMap lacks iteration methods (keys(), values(), entries()) and the size property, and it cannot be cleared (clear()). Available methods are get(), set(), has(), and delete().

// Add members using set
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2
// Accept an array as constructor argument
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"

Share your love