1196 lines
29 KiB
Markdown
1196 lines
29 KiB
Markdown
# es6-cheatsheet
|
||
|
||
A cheatsheet containing ES2015 [ES6] tips, tricks, best practices and code
|
||
snippet examples for your day to day workflow. Contributions are welcome!
|
||
|
||
## Table of Contents
|
||
|
||
- [var versus let / const](#var-versus-let--const)
|
||
- [Replacing IIFEs with Blocks](#replacing-iifes-with-blocks)
|
||
- [Arrow Functions](#arrow-functions)
|
||
- [Strings](#strings)
|
||
- [Destructuring](#destructuring)
|
||
- [Modules](#modules)
|
||
- [Parameters](#parameters)
|
||
- [Classes](#classes)
|
||
- [Symbols](#symbols)
|
||
- [Maps](#maps)
|
||
- [WeakMaps](#weakmaps)
|
||
- [Promises](#promises)
|
||
- [Generators](#generators)
|
||
- [Async Await](#async-await)
|
||
- [Getter/Setter functions](#getter-and-setter-functions)
|
||
|
||
## var versus let / const
|
||
|
||
> Besides `var`, we now have access to two new identifiers for storing values
|
||
—`let` and `const`. Unlike `var`, `let` and `const` statements are not hoisted
|
||
to the top of their enclosing scope.
|
||
|
||
An example of using `var`:
|
||
|
||
```javascript
|
||
var snack = 'Meow Mix';
|
||
|
||
function getFood(food) {
|
||
if (food) {
|
||
var snack = 'Friskies';
|
||
return snack;
|
||
}
|
||
return snack;
|
||
}
|
||
|
||
getFood(false); // undefined
|
||
```
|
||
|
||
However, observe what happens when we replace `var` using `let`:
|
||
|
||
```javascript
|
||
let snack = 'Meow Mix';
|
||
|
||
function getFood(food) {
|
||
if (food) {
|
||
let snack = 'Friskies';
|
||
return snack;
|
||
}
|
||
return snack;
|
||
}
|
||
|
||
getFood(false); // 'Meow Mix'
|
||
```
|
||
|
||
This change in behavior highlights that we need to be careful when refactoring
|
||
legacy code which uses `var`. Blindly replacing instances of `var` with `let`
|
||
may lead to unexpected behavior.
|
||
|
||
> **Note**: `let` and `const` are block scoped. Therefore, referencing
|
||
block-scoped identifiers before they are defined will produce
|
||
a `ReferenceError`.
|
||
|
||
```javascript
|
||
console.log(x); // ReferenceError: x is not defined
|
||
|
||
let x = 'hi';
|
||
```
|
||
|
||
> **Best Practice**: Leave `var` declarations inside of legacy code to denote
|
||
that it needs to be carefully refactored. When working on a new codebase, use
|
||
`let` for variables that will change their value over time, and `const` for
|
||
variables which cannot be reassigned.
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Replacing IIFEs with Blocks
|
||
|
||
> A common use of **Immediately Invoked Function Expressions** is to enclose
|
||
values within its scope. In ES6, we now have the ability to create block-based
|
||
scopes and therefore are not limited purely to function-based scope.
|
||
|
||
```javascript
|
||
(function () {
|
||
var food = 'Meow Mix';
|
||
}());
|
||
|
||
console.log(food); // Reference Error
|
||
```
|
||
|
||
Using ES6 Blocks:
|
||
|
||
```javascript
|
||
{
|
||
let food = 'Meow Mix';
|
||
};
|
||
|
||
console.log(food); // Reference Error
|
||
```
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Arrow Functions
|
||
|
||
Often times we have nested functions in which we would like to preserve the
|
||
context of `this` from its lexical scope. An example is shown below:
|
||
|
||
```javascript
|
||
function Person(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
Person.prototype.prefixName = function (arr) {
|
||
return arr.map(function (character) {
|
||
return this.name + character; // Cannot read property 'name' of undefined
|
||
});
|
||
};
|
||
```
|
||
|
||
One common solution to this problem is to store the context of `this` using
|
||
a variable:
|
||
|
||
```javascript
|
||
function Person(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
Person.prototype.prefixName = function (arr) {
|
||
var that = this; // Store the context of this
|
||
return arr.map(function (character) {
|
||
return that.name + character;
|
||
});
|
||
};
|
||
```
|
||
|
||
We can also pass in the proper context of `this`:
|
||
|
||
```javascript
|
||
function Person(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
Person.prototype.prefixName = function (arr) {
|
||
return arr.map(function (character) {
|
||
return this.name + character;
|
||
}, this);
|
||
};
|
||
```
|
||
|
||
As well as bind the context:
|
||
|
||
```javascript
|
||
function Person(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
Person.prototype.prefixName = function (arr) {
|
||
return arr.map(function (character) {
|
||
return this.name + character;
|
||
}.bind(this));
|
||
};
|
||
```
|
||
|
||
Using **Arrow Functions**, the lexical value of `this` isn't shadowed and we
|
||
can re-write the above as shown:
|
||
|
||
```javascript
|
||
function Person(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
Person.prototype.prefixName = function (arr) {
|
||
return arr.map(character => this.name + character);
|
||
};
|
||
```
|
||
|
||
> **Best Practice**: Use **Arrow Functions** whenever you need to preserve the
|
||
lexical value of `this`.
|
||
|
||
Arrow Functions are also more concise when used in function expressions which
|
||
simply return a value:
|
||
|
||
```javascript
|
||
var squares = arr.map(function (x) { return x * x }); // Function Expression
|
||
```
|
||
|
||
```javascript
|
||
const arr = [1, 2, 3, 4, 5];
|
||
const squares = arr.map(x => x * x); // Arrow Function for terser implementation
|
||
```
|
||
|
||
> **Best Practice**: Use **Arrow Functions** in place of function expressions
|
||
when possible.
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Strings
|
||
|
||
With ES6, the standard library has grown immensely. Along with these changes
|
||
are new methods which can be used on strings, such as `.includes()` and
|
||
`.repeat()`.
|
||
|
||
### .includes( )
|
||
|
||
```javascript
|
||
var string = 'food';
|
||
var substring = 'foo';
|
||
|
||
console.log(string.indexOf(substring) > -1);
|
||
```
|
||
|
||
Instead of checking for a return value `> -1` to denote string containment,
|
||
we can simply use `.includes()` which will return a boolean:
|
||
|
||
```javascript
|
||
const string = 'food';
|
||
const substring = 'foo';
|
||
|
||
console.log(string.includes(substring)); // true
|
||
```
|
||
|
||
### .repeat( )
|
||
|
||
```javascript
|
||
function repeat(string, count) {
|
||
var strings = [];
|
||
while(strings.length < count) {
|
||
strings.push(string);
|
||
}
|
||
return strings.join('');
|
||
}
|
||
```
|
||
|
||
In ES6, we now have access to a terser implementation:
|
||
|
||
```javascript
|
||
// String.repeat(numberOfRepetitions)
|
||
'meow'.repeat(3); // 'meowmeowmeow'
|
||
```
|
||
|
||
### Template Literals
|
||
|
||
Using **Template Literals**, we can now construct strings that have special
|
||
characters in them without needing to escape them explicitly.
|
||
|
||
```javascript
|
||
var text = "This string contains \"double quotes\" which are escaped.";
|
||
```
|
||
|
||
```javascript
|
||
let text = `This string contains "double quotes" which don't need to be escaped anymore.`;
|
||
```
|
||
|
||
**Template Literals** also support interpolation, which makes the task of
|
||
concatenating strings and values:
|
||
|
||
```javascript
|
||
var name = 'Tiger';
|
||
var age = 13;
|
||
|
||
console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
|
||
```
|
||
|
||
Much simpler:
|
||
|
||
```javascript
|
||
const name = 'Tiger';
|
||
const age = 13;
|
||
|
||
console.log(`My cat is named ${name} and is ${age} years old.`);
|
||
```
|
||
|
||
In ES5, we handled new lines as follows:
|
||
|
||
```javascript
|
||
var text = (
|
||
'cat\n' +
|
||
'dog\n' +
|
||
'nickelodeon'
|
||
);
|
||
```
|
||
|
||
Or:
|
||
|
||
```javascript
|
||
var text = [
|
||
'cat',
|
||
'dog',
|
||
'nickelodeon'
|
||
].join('\n');
|
||
```
|
||
|
||
**Template Literals** will preserve new lines for us without having to
|
||
explicitly place them in:
|
||
|
||
```javascript
|
||
let text = ( `cat
|
||
dog
|
||
nickelodeon`
|
||
);
|
||
```
|
||
|
||
**Template Literals** can accept expressions, as well:
|
||
|
||
```javascript
|
||
let today = new Date();
|
||
let text = `The time and date is ${today.toLocaleString()}`;
|
||
```
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Destructuring
|
||
|
||
Destructuring allows us to extract values from arrays and objects (even deeply
|
||
nested) and store them in variables with a more convenient syntax.
|
||
|
||
### Destructuring Arrays
|
||
|
||
```javascript
|
||
var arr = [1, 2, 3, 4];
|
||
var a = arr[0];
|
||
var b = arr[1];
|
||
var c = arr[2];
|
||
var d = arr[3];
|
||
```
|
||
|
||
```javascript
|
||
let [a, b, c, d] = [1, 2, 3, 4];
|
||
|
||
console.log(a); // 1
|
||
console.log(b); // 2
|
||
```
|
||
|
||
### Destructuring Objects
|
||
|
||
```javascript
|
||
var luke = { occupation: 'jedi', father: 'anakin' };
|
||
var occupation = luke.occupation; // 'jedi'
|
||
var father = luke.father; // 'anakin'
|
||
```
|
||
|
||
```javascript
|
||
let luke = { occupation: 'jedi', father: 'anakin' };
|
||
let {occupation, father} = luke;
|
||
|
||
console.log(occupation); // 'jedi'
|
||
console.log(father); // 'anakin'
|
||
```
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Modules
|
||
|
||
Prior to ES6, we used libraries such as [Browserify](http://browserify.org/)
|
||
to create modules on the client-side, and [require](https://nodejs.org/api/modules.html#modules_module_require_id)
|
||
in **Node.js**. With ES6, we can now directly use modules of all types
|
||
(AMD and CommonJS).
|
||
|
||
### Exporting in CommonJS
|
||
|
||
```javascript
|
||
module.exports = 1;
|
||
module.exports = { foo: 'bar' };
|
||
module.exports = ['foo', 'bar'];
|
||
module.exports = function bar () {};
|
||
```
|
||
|
||
### Exporting in ES6
|
||
|
||
With ES6, we have various flavors of exporting. We can perform
|
||
**Named Exports**:
|
||
|
||
```javascript
|
||
export let name = 'David';
|
||
export let age = 25;
|
||
```
|
||
|
||
As well as **exporting a list** of objects:
|
||
|
||
```javascript
|
||
function sumTwo(a, b) {
|
||
return a + b;
|
||
}
|
||
|
||
function sumThree(a, b, c) {
|
||
return a + b + c;
|
||
}
|
||
|
||
export { sumTwo, sumThree };
|
||
```
|
||
|
||
We can also export functions, objects and values (etc.) simply by using the `export` keyword:
|
||
|
||
```javascript
|
||
export function sumTwo(a, b) {
|
||
return a + b;
|
||
}
|
||
|
||
export function sumThree(a, b, c) {
|
||
return a + b + c;
|
||
}
|
||
```
|
||
|
||
And lastly, we can **export default bindings**:
|
||
|
||
```javascript
|
||
function sumTwo(a, b) {
|
||
return a + b;
|
||
}
|
||
|
||
function sumThree(a, b, c) {
|
||
return a + b + c;
|
||
}
|
||
|
||
let api = {
|
||
sumTwo,
|
||
sumThree
|
||
};
|
||
|
||
export default api;
|
||
|
||
/* Which is the same as
|
||
* export { api as default };
|
||
*/
|
||
```
|
||
|
||
> **Best Practices**: Always use the `export default` method at **the end** of
|
||
the module. It makes it clear what is being exported, and saves time by having
|
||
to figure out what name a value was exported as. More so, the common practice
|
||
in CommonJS modules is to export a single value or object. By sticking to this
|
||
paradigm, we make our code easily readable and allow ourselves to interpolate
|
||
between CommonJS and ES6 modules.
|
||
|
||
### Importing in ES6
|
||
|
||
ES6 provides us with various flavors of importing. We can import an entire file:
|
||
|
||
```javascript
|
||
import 'underscore';
|
||
```
|
||
|
||
> It is important to note that simply **importing an entire file will execute
|
||
all code at the top level of that file**.
|
||
|
||
Similar to Python, we have named imports:
|
||
|
||
```javascript
|
||
import { sumTwo, sumThree } from 'math/addition';
|
||
```
|
||
|
||
We can also rename the named imports:
|
||
|
||
```javascript
|
||
import {
|
||
sumTwo as addTwoNumbers,
|
||
sumThree as sumThreeNumbers
|
||
} from 'math/addition';
|
||
```
|
||
|
||
In addition, we can **import all the things** (also called namespace import):
|
||
|
||
```javascript
|
||
import * as util from 'math/addition';
|
||
```
|
||
|
||
Lastly, we can import a list of values from a module:
|
||
|
||
```javascript
|
||
import * as additionUtil from 'math/addition';
|
||
const { sumTwo, sumThree } = additionUtil;
|
||
```
|
||
Importing from the default binding like this:
|
||
|
||
```javascript
|
||
import api from 'math/addition';
|
||
// Same as: import { default as api } from 'math/addition';
|
||
```
|
||
|
||
While it is better to keep the exports simple, but we can sometimes mix default import and mixed import if needed.
|
||
When we are exporting like this:
|
||
|
||
```javascript
|
||
// foos.js
|
||
export { foo as default, foo1, foo2 };
|
||
```
|
||
|
||
We can import them like the following:
|
||
|
||
```javascript
|
||
import foo, { foo1, foo2 } from 'foos';
|
||
```
|
||
|
||
When importing a module exported using commonjs syntax (such as React) we can do:
|
||
|
||
```javascript
|
||
import React from 'react';
|
||
const { Component, PropTypes } = React;
|
||
```
|
||
|
||
This can also be simplified further, using:
|
||
|
||
```javascript
|
||
import React, { Component, PropTypes } from 'react';
|
||
```
|
||
|
||
> **Note**: Values that are exported are **bindings**, not references.
|
||
Therefore, changing the binding of a variable in one module will affect the
|
||
value within the exported module. Avoid changing the public interface of these
|
||
exported values.
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Parameters
|
||
|
||
In ES5, we had varying ways to handle functions which needed **default values**,
|
||
**indefinite arguments**, and **named parameters**. With ES6, we can accomplish
|
||
all of this and more using more concise syntax.
|
||
|
||
### Default Parameters
|
||
|
||
```javascript
|
||
function addTwoNumbers(x, y) {
|
||
x = x || 0;
|
||
y = y || 0;
|
||
return x + y;
|
||
}
|
||
```
|
||
|
||
In ES6, we can simply supply default values for parameters in a function:
|
||
|
||
```javascript
|
||
function addTwoNumbers(x=0, y=0) {
|
||
return x + y;
|
||
}
|
||
```
|
||
|
||
```javascript
|
||
addTwoNumbers(2, 4); // 6
|
||
addTwoNumbers(2); // 2
|
||
addTwoNumbers(); // 0
|
||
```
|
||
|
||
### Rest Parameters
|
||
|
||
In ES5, we handled an indefinite number of arguments like so:
|
||
|
||
```javascript
|
||
function logArguments() {
|
||
for (var i=0; i < arguments.length; i++) {
|
||
console.log(arguments[i]);
|
||
}
|
||
}
|
||
```
|
||
|
||
Using the **rest** operator, we can pass in an indefinite amount of arguments:
|
||
|
||
```javascript
|
||
function logArguments(...args) {
|
||
for (let arg of args) {
|
||
console.log(arg);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Named Parameters
|
||
|
||
One of the patterns in ES5 to handle named parameters was to use the **options
|
||
object** pattern, adopted from jQuery.
|
||
|
||
```javascript
|
||
function initializeCanvas(options) {
|
||
var height = options.height || 600;
|
||
var width = options.width || 400;
|
||
var lineStroke = options.lineStroke || 'black';
|
||
}
|
||
```
|
||
|
||
We can achieve the same functionality using destructuring as a formal parameter
|
||
to a function:
|
||
|
||
```javascript
|
||
function initializeCanvas(
|
||
{ height=600, width=400, lineStroke='black'}) {
|
||
// Use variables height, width, lineStroke here
|
||
}
|
||
```
|
||
|
||
If we want to make the entire value optional, we can do so by destructuring an
|
||
empty object:
|
||
|
||
```javascript
|
||
function initializeCanvas(
|
||
{ height=600, width=400, lineStroke='black'} = {}) {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### Spread Operator
|
||
|
||
In ES5, we could find the max of values in an array by using the `apply` method on `Math.max` like this:
|
||
```javascript
|
||
Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
|
||
```
|
||
|
||
In ES6, we can now use the spread operator to pass an array of values to be used as
|
||
parameters to a function:
|
||
|
||
```javascript
|
||
Math.max(...[-1, 100, 9001, -32]); // 9001
|
||
```
|
||
|
||
We can concat array literals easily with this intuitive syntax:
|
||
|
||
```javascript
|
||
let cities = ['San Francisco', 'Los Angeles'];
|
||
let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']
|
||
```
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Classes
|
||
|
||
Prior to ES6, we implemented Classes by creating a constructor function and
|
||
adding properties by extending the prototype:
|
||
|
||
```javascript
|
||
function Person(name, age, gender) {
|
||
this.name = name;
|
||
this.age = age;
|
||
this.gender = gender;
|
||
}
|
||
|
||
Person.prototype.incrementAge = function () {
|
||
return this.age += 1;
|
||
};
|
||
```
|
||
|
||
And created extended classes by the following:
|
||
|
||
```javascript
|
||
function Personal(name, age, gender, occupation, hobby) {
|
||
Person.call(this, name, age, gender);
|
||
this.occupation = occupation;
|
||
this.hobby = hobby;
|
||
}
|
||
|
||
Personal.prototype = Object.create(Person.prototype);
|
||
Personal.prototype.constructor = Personal;
|
||
Personal.prototype.incrementAge = function () {
|
||
Person.prototype.incrementAge.call(this);
|
||
this.age += 20;
|
||
console.log(this.age);
|
||
};
|
||
```
|
||
|
||
ES6 provides much needed syntactic sugar for doing this under the hood. We can
|
||
create Classes directly:
|
||
|
||
```javascript
|
||
class Person {
|
||
constructor(name, age, gender) {
|
||
this.name = name;
|
||
this.age = age;
|
||
this.gender = gender;
|
||
}
|
||
|
||
incrementAge() {
|
||
this.age += 1;
|
||
}
|
||
}
|
||
```
|
||
|
||
And extend them using the `extends` keyword:
|
||
|
||
```javascript
|
||
class Personal extends Person {
|
||
constructor(name, age, gender, occupation, hobby) {
|
||
super(name, age, gender);
|
||
this.occupation = occupation;
|
||
this.hobby = hobby;
|
||
}
|
||
|
||
incrementAge() {
|
||
super.incrementAge();
|
||
this.age += 20;
|
||
console.log(this.age);
|
||
}
|
||
}
|
||
```
|
||
|
||
> **Best Practice**: While the syntax for creating classes in ES6 obscures how
|
||
implementation and prototypes work under the hood, it is a good feature for
|
||
beginners and allows us to write cleaner code.
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Symbols
|
||
|
||
Symbols have existed prior to ES6, but now we have a public interface to using
|
||
them directly. Symbols are immutable and unique and can be used as keys in any hash.
|
||
|
||
### Symbol( )
|
||
|
||
Calling `Symbol()` or `Symbol(description)` will create a unique symbol that cannot be looked up
|
||
globally. A Use case for `Symbol()` is to patch objects or namespaces from third parties with your own
|
||
logic, but be confident that you won't collide with updates to that library. For example,
|
||
if you wanted to add a method `refreshComponent` to the `React.Component` class, and be certain that
|
||
you didn't trample a method they add in a later update:
|
||
|
||
```javascript
|
||
const refreshComponent = Symbol();
|
||
|
||
React.Component.prototype[refreshComponent] = () => {
|
||
// do something
|
||
}
|
||
```
|
||
|
||
|
||
### Symbol.for(key)
|
||
|
||
`Symbol.for(key)` will create a Symbol that is still immutable and unique, but can be looked up globally.
|
||
Two identical calls to `Symbol.for(key)` will return the same Symbol instance. NOTE: This is not true for
|
||
`Symbol(description)`:
|
||
|
||
```javascript
|
||
Symbol('foo') === Symbol('foo') // false
|
||
Symbol.for('foo') === Symbol('foo') // false
|
||
Symbol.for('foo') === Symbol.for('foo') // true
|
||
```
|
||
|
||
A common use case for Symbols, and in particular with `Symbol.for(key)` is for interoperability. This can be
|
||
achieved by having your code look for a Symbol member on object arguments from third parties that contain some
|
||
known interface. For example:
|
||
|
||
```javascript
|
||
function reader(obj) {
|
||
const specialRead = Symbol.for('specialRead');
|
||
if (obj[specialRead]) {
|
||
const reader = obj[specialRead]();
|
||
// do something with reader
|
||
} else {
|
||
throw new TypeError('object cannot be read');
|
||
}
|
||
}
|
||
```
|
||
|
||
And then in another library:
|
||
|
||
```javascript
|
||
const specialRead = Symbol.for('specialRead');
|
||
|
||
class SomeReadableType {
|
||
[specialRead]() {
|
||
const reader = createSomeReaderFrom(this);
|
||
return reader;
|
||
}
|
||
}
|
||
```
|
||
|
||
> A notable example of Symbol use for interoperability is `Symbol.iterator` which exists on all iterable
|
||
types in ES6: Arrays, strings, generators, etc. When called as a method it returns an object with an Iterator
|
||
interface.
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Maps
|
||
|
||
**Maps** is a much needed data structure in JavaScript. Prior to ES6, we created
|
||
**hash** maps through objects:
|
||
|
||
```javascript
|
||
var map = new Object();
|
||
map[key1] = 'value1';
|
||
map[key2] = 'value2';
|
||
```
|
||
|
||
However, this does not protect us from accidentally overriding functions with
|
||
specific property names:
|
||
|
||
```javascript
|
||
> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
|
||
> TypeError: Property 'hasOwnProperty' is not a function
|
||
```
|
||
|
||
Actual **Maps** allow us to `set`, `get` and `search` for values (and much more).
|
||
|
||
```javascript
|
||
let map = new Map();
|
||
> map.set('name', 'david');
|
||
> map.get('name'); // david
|
||
> map.has('name'); // true
|
||
```
|
||
|
||
The most amazing part of Maps is that we are no longer limited to just using
|
||
strings. We can now use any type as a key, and it will not be type-cast to
|
||
a string.
|
||
|
||
```javascript
|
||
let map = new Map([
|
||
['name', 'david'],
|
||
[true, 'false'],
|
||
[1, 'one'],
|
||
[{}, 'object'],
|
||
[function () {}, 'function']
|
||
]);
|
||
|
||
for (let key of map.keys()) {
|
||
console.log(typeof key);
|
||
// > string, boolean, number, object, function
|
||
}
|
||
```
|
||
|
||
> **Note**: Using non-primitive values such as functions or objects won't work
|
||
when testing equality using methods such as `map.get()`. As such, stick to
|
||
primitive values such as Strings, Booleans and Numbers.
|
||
|
||
We can also iterate over maps using `.entries()`:
|
||
|
||
```javascript
|
||
for (let [key, value] of map.entries()) {
|
||
console.log(key, value);
|
||
}
|
||
```
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## WeakMaps
|
||
|
||
In order to store private data versions < ES6, we had various ways of doing this.
|
||
One such method was using naming conventions:
|
||
|
||
```javascript
|
||
class Person {
|
||
constructor(age) {
|
||
this._age = age;
|
||
}
|
||
|
||
_incrementAge() {
|
||
this._age += 1;
|
||
}
|
||
}
|
||
```
|
||
|
||
But naming conventions can cause confusion in a codebase and are not always
|
||
going to be upheld. Instead, we can use WeakMaps to store our values:
|
||
|
||
```javascript
|
||
let _age = new WeakMap();
|
||
class Person {
|
||
constructor(age) {
|
||
_age.set(this, age);
|
||
}
|
||
|
||
incrementAge() {
|
||
let age = _age.get(this) + 1;
|
||
_age.set(this, age);
|
||
if (age > 50) {
|
||
console.log('Midlife crisis');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
The cool thing about using WeakMaps to store our private data is that their
|
||
keys do not give away the property names, which can be seen by using
|
||
`Reflect.ownKeys()`:
|
||
|
||
```javascript
|
||
> const person = new Person(50);
|
||
> person.incrementAge(); // 'Midlife crisis'
|
||
> Reflect.ownKeys(person); // []
|
||
```
|
||
|
||
A more practical example of using WeakMaps is to store data which is associated
|
||
to a DOM element without having to pollute the DOM itself:
|
||
|
||
```javascript
|
||
let map = new WeakMap();
|
||
let el = document.getElementById('someElement');
|
||
|
||
// Store a weak reference to the element with a key
|
||
map.set(el, 'reference');
|
||
|
||
// Access the value of the element
|
||
let value = map.get(el); // 'reference'
|
||
|
||
// Remove the reference
|
||
el.parentNode.removeChild(el);
|
||
el = null;
|
||
|
||
// map is empty, since the element is destroyed
|
||
```
|
||
|
||
As shown above, once the object is destroyed by the garbage collector,
|
||
the WeakMap will automatically remove the key-value pair which was identified
|
||
by that object.
|
||
|
||
> **Note**: To further illustrate the usefulness of this example, consider how
|
||
jQuery stores a cache of objects corresponding to DOM elements which have
|
||
references. Using WeakMaps, jQuery can automatically free up any memory that
|
||
was associated with a particular DOM element once it has been removed from the
|
||
document. In general, WeakMaps are very useful for any library that wraps DOM
|
||
elements.
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Promises
|
||
|
||
Promises allow us to turn our horizontal code (callback hell):
|
||
|
||
```javascript
|
||
func1(function (value1) {
|
||
func2(value1, function (value2) {
|
||
func3(value2, function (value3) {
|
||
func4(value3, function (value4) {
|
||
func5(value4, function (value5) {
|
||
// Do something with value 5
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
```
|
||
|
||
Into vertical code:
|
||
|
||
```javascript
|
||
func1(value1)
|
||
.then(func2)
|
||
.then(func3)
|
||
.then(func4)
|
||
.then(func5, value5 => {
|
||
// Do something with value 5
|
||
});
|
||
```
|
||
|
||
Prior to ES6, we used [bluebird](https://github.com/petkaantonov/bluebird) or
|
||
[Q](https://github.com/kriskowal/q). Now we have Promises natively:
|
||
|
||
```javascript
|
||
new Promise((resolve, reject) =>
|
||
reject(new Error('Failed to fulfill Promise')))
|
||
.catch(reason => console.log(reason));
|
||
```
|
||
|
||
Where we have two handlers, **resolve** (a function called when the Promise is
|
||
**fulfilled**) and **reject** (a function called when the Promise is **rejected**).
|
||
|
||
> **Benefits of Promises**: Error Handling using a bunch of nested callbacks
|
||
can get chaotic. Using Promises, we have a clear path to bubbling errors up
|
||
and handling them appropriately. Moreover, the value of a Promise after it has
|
||
been resolved/rejected is immutable - it will never change.
|
||
|
||
Here is a practical example of using Promises:
|
||
|
||
```javascript
|
||
var request = require('request');
|
||
|
||
return new Promise((resolve, reject) => {
|
||
request.get(url, (error, response, body) => {
|
||
if (body) {
|
||
resolve(JSON.parse(body));
|
||
} else {
|
||
resolve({});
|
||
}
|
||
});
|
||
});
|
||
```
|
||
|
||
We can also **parallelize** Promises to handle an array of asynchronous
|
||
operations by using `Promise.all()`:
|
||
|
||
```javascript
|
||
let urls = [
|
||
'/api/commits',
|
||
'/api/issues/opened',
|
||
'/api/issues/assigned',
|
||
'/api/issues/completed',
|
||
'/api/issues/comments',
|
||
'/api/pullrequests'
|
||
];
|
||
|
||
let promises = urls.map((url) => {
|
||
return new Promise((resolve, reject) => {
|
||
$.ajax({ url: url })
|
||
.done((data) => {
|
||
resolve(data);
|
||
});
|
||
});
|
||
});
|
||
|
||
Promise.all(promises)
|
||
.then((results) => {
|
||
// Do something with results of all our promises
|
||
});
|
||
```
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Generators
|
||
|
||
Similar to how [Promises](https://github.com/DrkSephy/es6-cheatsheet#promises) allow us to avoid
|
||
[callback hell](http://callbackhell.com/), Generators allow us to flatten our code - giving our
|
||
asynchronous code a synchronous feel. Generators are essentially functions which we can
|
||
[pause their execution](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield)
|
||
and subsequently return the value of an expression.
|
||
|
||
A simple example of using generators is shown below:
|
||
|
||
```javascript
|
||
function* sillyGenerator() {
|
||
yield 1;
|
||
yield 2;
|
||
yield 3;
|
||
yield 4;
|
||
}
|
||
|
||
var generator = sillyGenerator();
|
||
> console.log(generator.next()); // { value: 1, done: false }
|
||
> console.log(generator.next()); // { value: 2, done: false }
|
||
> console.log(generator.next()); // { value: 3, done: false }
|
||
> console.log(generator.next()); // { value: 4, done: false }
|
||
```
|
||
|
||
Where [next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next)
|
||
will allow us to push our generator forward and evaluate a new expression. While the above example is extremely
|
||
contrived, we can utilize Generators to write asynchronous code in a synchronous manner:
|
||
|
||
```javascript
|
||
// Hiding asynchronousity with Generators
|
||
|
||
function request(url) {
|
||
getJSON(url, function(response) {
|
||
generator.next(response);
|
||
});
|
||
}
|
||
```
|
||
|
||
And here we write a generator function that will return our data:
|
||
|
||
```javascript
|
||
function* getData() {
|
||
var entry1 = yield request('http://some_api/item1');
|
||
var data1 = JSON.parse(entry1);
|
||
var entry2 = yield request('http://some_api/item2');
|
||
var data2 = JSON.parse(entry2);
|
||
}
|
||
```
|
||
|
||
By the power of `yield`, we are guaranteed that `entry1` will have the data needed to be parsed and stored
|
||
in `data1`.
|
||
|
||
While generators allow us to write asynchronous code in a synchronous manner, there is no clear
|
||
and easy path for error propagation. As such, as we can augment our generator with Promises:
|
||
|
||
```javascript
|
||
function request(url) {
|
||
return new Promise((resolve, reject) => {
|
||
getJSON(url, resolve);
|
||
});
|
||
}
|
||
```
|
||
|
||
And we write a function which will step through our generator using `next` which in turn will utilize our
|
||
`request` method above to yield a Promise:
|
||
|
||
```javascript
|
||
function iterateGenerator(gen) {
|
||
var generator = gen();
|
||
(function iterate(val) {
|
||
var ret = generator.next();
|
||
if(!ret.done) {
|
||
ret.value.then(iterate);
|
||
}
|
||
})();
|
||
}
|
||
```
|
||
|
||
By augmenting our Generator with Promises, we have a clear way of propagating errors through the use of our
|
||
Promise `.catch` and `reject`. To use our newly augmented Generator, it is as simple as before:
|
||
|
||
```javascript
|
||
iterateGenerator(function* getData() {
|
||
var entry1 = yield request('http://some_api/item1');
|
||
var data1 = JSON.parse(entry1);
|
||
var entry2 = yield request('http://some_api/item2');
|
||
var data2 = JSON.parse(entry2);
|
||
});
|
||
```
|
||
|
||
We were able to reuse our implementation to use our Generator as before, which shows their power. While Generators
|
||
and Promises allow us to write asynchronous code in a synchronous manner while retaining the ability to propagate
|
||
errors in a nice way, we can actually begin to utilize a simpler construction that provides the same benefits:
|
||
[async-await](https://github.com/DrkSephy/es6-cheatsheet#async-await).
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
|
||
## Async Await
|
||
|
||
While this is actually an upcoming ES2016 feature, `async await` allows us to perform the same thing we accomplished
|
||
using Generators and Promises with less effort:
|
||
|
||
```javascript
|
||
var request = require('request');
|
||
|
||
function getJSON(url) {
|
||
return new Promise(function(resolve, reject) {
|
||
request(url, function(error, response, body) {
|
||
resolve(body);
|
||
});
|
||
});
|
||
}
|
||
|
||
async function main() {
|
||
var data = await getJSON();
|
||
console.log(data); // NOT undefined!
|
||
}
|
||
|
||
main();
|
||
```
|
||
|
||
Under the hood, it performs similarly to Generators. I highly recommend using them over Generators + Promises. A great resource
|
||
for getting up and running with ES7 and Babel can be found [here](http://masnun.com/2015/11/11/using-es7-asyncawait-today-with-babel.html).
|
||
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup>
|
||
## Getter and setter functions
|
||
|
||
ES6 has started supporting getter and setter functions within classes. Using the following example:
|
||
|
||
```javascript
|
||
class Employee {
|
||
|
||
constructor(name) {
|
||
this._name = name;
|
||
}
|
||
|
||
get name() {
|
||
if(this._name) {
|
||
return 'Mr. ' + this._name.toUpperCase();
|
||
} else {
|
||
return undefined;
|
||
}
|
||
}
|
||
|
||
set name(newName) {
|
||
if (newName == this._name) {
|
||
console.log('I already have this name.');
|
||
} else if (newName) {
|
||
this._name = newName;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
var emp = new Employee("James Bond");
|
||
|
||
// uses the get method in the background
|
||
if (emp.name) {
|
||
console.log(emp.name); // Mr. JAMES BOND
|
||
}
|
||
|
||
// uses the setter in the background
|
||
emp.name = "Bond 007";
|
||
console.log(emp.name); // Mr. BOND 007
|
||
```
|
||
|
||
Latest browsers are also supporting getter/setter functions in Objects and we can use them for computed properties, adding listeners and preprocessing before setting/getting:
|
||
|
||
```javascript
|
||
var person = {
|
||
firstName: 'James',
|
||
lastName: 'Bond',
|
||
get fullName() {
|
||
console.log('Getting FullName');
|
||
return this.firstName + ' ' + this.lastName;
|
||
},
|
||
set fullName (name) {
|
||
console.log('Setting FullName');
|
||
var words = name.toString().split(' ');
|
||
this.firstName = words[0] || '';
|
||
this.lastName = words[1] || '';
|
||
}
|
||
}
|
||
|
||
person.fullName; // James Bond
|
||
person.fullName = 'Bond 007';
|
||
person.fullName; // Bond 007
|
||
```
|
||
<sup>[(back to table of contents)](#table-of-contents)</sup> |