ES6 in Depth

Record some useful points after reading ES6 in Depth.

Assignment Destructuring

By default, properties that aren’t found will be undefined

1
2
3
var {foo} = {bar: 'baz'}
console.log(foo)
// <- undefined

If you’re trying to access a deeply nested property of a parent that doesn’t exist, then you’ll get an exception

1
2
var {foo:{bar}} = {baz: 'ouch'}
// <- Exception

A great fit for destructuring are things like regular expressions, where you would just love to name parameters without having to resort to index numbers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getUrlParts (url) {
var magic = /^(https?):\/\/(ponyfoo\.com)(\/articles\/([a-z0-9-]+))$/
return magic.exec(url)
}
var parts = getUrlParts('http://ponyfoo.com/articles/es6-destructuring-in-depth')
var [,protocol,host,pathname,slug] = parts
console.log(protocol)
// <- 'http'
console.log(host)
// <- 'ponyfoo.com'
console.log(pathname)
// <- '/articles/es6-destructuring-in-depth'
console.log(slug)
// <- 'es6-destructuring-in-depth'

Spread Operator and Rest Parameters

...rest must be the last parameter in the list

Use Case ES5 ES6
Concatenation [1, 2].concat(more) [1, 2, …more]
Push onto list list.push.apply(list, [3, 4]) list.push(…[3, 4])
Destructuring a = list[0], rest = list.slice(1) [a, …rest] = list
new + apply new (Date.bind.apply(Date, [null,2015,8,1])) new Date(…[2015,8,1])

Arrow Functions

1
2
3
4
5
6
7
8
9
10
11
12
[1, 2, 3].map(n => { number: n })
// [undefined, undefined, undefined]
[1, 2, 3].map(n => { number: n, something: 'else' })
// <- SyntaxError
[1, 2, 3].map(n => ({ number: n }))
// <- [{ number: 1 }, { number: 2 }, { number: 3 }]
[1, 2, 3].map(n => ({ number: n, something: 'else' }))
/* <- [
{ number: 1, something: 'else' },
{ number: 2, something: 'else' },
{ number: 3, something: 'else' }]
*/

Let, Const and the “Temporal Dead Zone” (TDZ)

1
2
3
there = 'far away'
// <- ReferenceError: there is not defined
let there = 'dragons'

Declaring a method that references there before it’s defined is okay, as long as the method doesn’t get executed while there is in the TDZ, and there will be in the TDZ for as long as the let there statement isn’t reached (while the scope has been entered).

1
2
3
4
5
6
function readThere () {
return there
}
let there = 'dragons'
console.log(readThere())
// <- 'dragons'

But this snippet will, because access to there occurs before leaving the TDZ for there.

1
2
3
4
5
6
function readThere () {
return there
}
console.log(readThere())
// ReferenceError: there is not defined
let there = 'dragons'

This snippet still works because it still leaves the TDZ before accessing there in any way.

1
2
3
4
5
6
function readThere () {
return there
}
let there
console.log(readThere())
// <- undefined

Symbols

1
var mystery = Symbol('this is a descriptive description')

Symbols are immutable and unique.

1
2
3
4
console.log(Symbol() === Symbol())
// <- false
console.log(Symbol('foo') === Symbol('foo'))
// <- false
1
2
Symbol.for('foo') === Symbol.for('foo')
// <- true
1
2
3
var symbol = Symbol.for('foo')
console.log(Symbol.keyFor(symbol))
// <- 'foo'

Why Would I Want Symbols?

  • You can use symbols to avoid name clashes in property keys.
  • They’re useful in situations where you want to define metadata that shouldn’t be part of iterable sequences for arrays or any iterable objects.
  • Defining protocols – just like there’s Symbol.iterator which allows you to define how an object can be iterated.

Map

One of the important differences is that you’re able to use anything for the keys.

1
2
3
4
var map = new Map()
map.set(new Date(), function today () {})
map.set(() => 'key', { pony: 'foo' })
map.set(Symbol('items'), [1, 2])
  • Use map.set(key, value) to add entries
  • Use map.get(key) to get an entry
  • Check for a key using map.has(key)
  • Remove entries with map.delete(key)

WeakMaps

  • WeakMap isn’t iterable, so you don’t get enumeration methods like .forEach, .clear, and others you had in Map
  • WeakMap keys must be reference types. You can’t use value types like symbols, numbers, or strings as keys
  • That last point means WeakMap is great at keeping around metadata for objects, while those objects are still in use
  • You avoid memory leaks, without manual reference counting
1
2
3
4
5
6
7
8
9
var date = new Date()
var map = new WeakMap([[date, 'foo'], [() => 'bar', 'baz']])
console.log(map.has(date))
// <- true
console.log(map.get(date))
// <- 'foo'
map.delete(date)
console.log(map.has(date))
// <- false

Sets

  • Set doesn’t have keys, there’s only values
  • set.set(value) doesn’t look right, so we have set.add(value) instead
  • Sets can’t have duplicate values because the values are also used as keys
1
2
3
4

var set = new Set([1, 2, 3, 4, 4])
console.log([...set])
// <- [1, 2, 3, 4]

WeakSets

Its values must be unique object references.

1
2
3
var set = new WeakSet()
set.add({})
set.add(new Date())

Number

  • Number.isInteger checks if input is a Number value that doesn’t have a decimal part
  • Number.EPSILON helps figure out negligible differences between two numbers – e.g. 0.1 + 0.2 and 0.3
  • Number.MAX_SAFE_INTEGER is the largest integer that can be safely and precisely represented in JavaScript
  • Number.MIN_SAFE_INTEGER is the smallest integer that can be safely and precisely represented in JavaScript
  • Number.isSafeInteger checks whether an integer is within those bounds, able to be represented safely and precisely

Array

  • Array.prototype.copyWithin – copies a sequence of array elements into somewhere else in the array

    1
    2
    3
    4
    var items = [1, 2, 3, ,,,,,,,]
    // <- [1, 2, 3, undefined x 7]
    items.copyWithin(6, 1, 3)
    // <- [1, 2, 3, undefined × 3, 2, 3, undefined × 2]
  • Array.prototype.fill – fills all elements of an existing array with the provided value

    1
    2
    3
    4
    ['a', 'b', 'c',,,].fill(0, 2)
    // <- ['a', 'b', 0, 0, 0]
    new Array(5).fill(0, 0, 3)
    // <- [0, 0, 0, undefined x 2]
  • Array.prototype.find – returns the first item to satisfy a callback

    1
    2
    3
    4
    5
    6
    [1, 2, 3, 4, 5].find(item => item > 2)
    // <- 3
    [1, 2, 3, 4, 5].find((item, i) => i === 3)
    // <- 4
    [1, 2, 3, 4, 5].find(item => item === Infinity)
    // <- undefined
  • Array.prototype.findIndex – returns the index of the first item to satisfy a callback

    1
    2
    3
    4
    5
    6
    [1, 2, 3, 4, 5].find(item => item > 2)
    // <- 2
    [1, 2, 3, 4, 5].find((item, i) => i === 3)
    // <- 3
    [1, 2, 3, 4, 5].find(item => item === Infinity)
    // <- -1
  • Array.prototype.keys – returns an iterator that yields a sequence holding the keys for the array

    1
    2
    [1, 2, 3].keys()
    // <- ArrayIterator {}
    1
    2
    3
    4
    5
    6
    for (let key of [1, 2, 3].keys()) {
    console.log(key)
    // <- 0
    // <- 1
    // <- 2
    }
    1
    2
    3
    4
    [...new Array(3).keys()]
    // <- [0, 1, 2]
    Object.keys(new Array(3))
    // <- []
  • Array.prototype.values – returns an iterator that yields a sequence holding the values for the array

    1
    2
    [1, 2, 3].values()
    // <- ArrayIterator {}
    1
    2
    [...[1, 2, 3].values()]
    // <- [1, 2, 3]
  • Array.prototype.entries – returns an iterator that yields a sequence holding key value pairs for the array

    1
    2
    ['a', 'b', 'c'].entries()
    // <- ArrayIterator {}
    1
    2
    [...['a', 'b', 'c'].entries()]
    // <- [[0, 'a'], [1, 'b'], [2, 'c']]
  • Array.prototype[Symbol.iterator] – exactly the same as the Array.prototype.values method

    1
    2
    [...['a', 'b', 'c'][Symbol.iterator]()]
    // <- ['a', 'b', 'c']

Object

  • Object.assign – recursive shallow overwrite for properties from target, …objects

    1
    2
    Object.assign({ a: 1, b: 2 }, { a: 3 }, { c: 4 })
    // <- { a: 3, b: 2, c: 4 }
  • Object.is – like using the === operator programmatically, except it’s true for NaN vs NaN and false for +0 vs -0

    1
    2
    3
    4
    Object.is('foo', 'foo')
    // <- true
    Object.is({}, {})
    // <- false
  • Object.getOwnPropertySymbols – returns all own property symbols found on an object

    1
    2
    3
    4
    5
    6
    7
    8
    var a = {
    [Symbol('b')]: 'c',
    [Symbol('d')]: 'e',
    'f': 'g',
    'h': 'i'
    }
    Object.getOwnPropertySymbols(a)
    // <- [Symbol(b), Symbol(d)]
  • Object.setPrototypeOf – changes prototype.