Record some useful points after reading ES6 in Depth.
Assignment Destructuring
By default, properties that aren’t found will be undefined
1 | var {foo} = {bar: 'baz'} |
If you’re trying to access a deeply nested property of a parent that doesn’t exist, then you’ll get an exception
1 | var {foo:{bar}} = {baz: 'ouch'} |
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 | function getUrlParts (url) { |
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 | [1, 2, 3].map(n => { number: n }) |
Let, Const and the “Temporal Dead Zone” (TDZ)
1 | there = 'far away' |
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 | function readThere () { |
But this snippet will, because access to there occurs before leaving the TDZ for there.
1 | function readThere () { |
This snippet still works because it still leaves the TDZ before accessing there in any way.
1 | function readThere () { |
Symbols
1 | var mystery = Symbol('this is a descriptive description') |
Symbols are immutable and unique.
1 | console.log(Symbol() === Symbol()) |
1 | Symbol.for('foo') === Symbol.for('foo') |
1 | var symbol = Symbol.for('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 | var map = new Map() |
- 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 | var date = new Date() |
Sets
- Set doesn’t have keys, there’s only values
set.set(value)doesn’t look right, so we haveset.add(value)instead- Sets can’t have duplicate values because the values are also used as keys
1 |
|
WeakSets
Its values must be unique object references.
1 | var set = new WeakSet() |
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
4var 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)
// <- undefinedArray.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)
// <- -1Array.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
6for (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
2Object.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
4Object.is('foo', 'foo')
// <- true
Object.is({}, {})
// <- falseObject.getOwnPropertySymbols – returns all own property symbols found on an object
1
2
3
4
5
6
7
8var a = {
[Symbol('b')]: 'c',
[Symbol('d')]: 'e',
'f': 'g',
'h': 'i'
}
Object.getOwnPropertySymbols(a)
// <- [Symbol(b), Symbol(d)]Object.setPrototypeOf – changes prototype.