This repository is created to give a quick overview for anyone who is interested in the new features that ES6 provides. For more detailed information about each feature, please visit https://developer.mozilla.org/en-US/docs/Web/JavaScript. In order to stay up to date with the browsers compatibility, visit https://kangax.github.io/compat-table/es6/.
- Variables and Parameters
- Classes
- Functional JavaScript
- Built-in Objects
- Asynchronous JavaScript
- ES6 Objects
- Modules
function doWork () {
for (let i = 0; i < 10; i++) {}
return i;
}
doWork(); // ReferenceError: i is not defined(…)
const MIN_LENGTH = 5;
MIN_LENGTH = 7; // TypeError: Assignment to constant variable.
let x = 3;
let y = 5;
[x, y] = [y, x]; // x = 5, y = 3
let [x, y] = [3, 5];
function doWork () {
return [1, 2, 3];
}
let [, x, y, z] = doWork(); // x = 2, y = 3, z = undefined
function doWork () {
return {
firstName: 'test',
lastName: 'testing',
misc: {
facebook: 'testFB'
}
};
}
let {
firstName: first,
lastName,
misc: { facebook: fb }
} = doWork(); // first = 'test', lastName = 'testing', fb = 'testFB'
let doWork = function (config, {data, hasCache}) {
return data;
}
let foo = doWork(
'someUrl/test', {
data: 'test',
hasCache: false
}
); //foo = 'test'
let doWork = function (name = 'test') {
return name;
}
let foo = doWork(''); // foo = ''
let bar = doWork(null); // bar = null
let baz = doWork(undefined); // baz = 'test'
let qux = doWork(); // qux = 'test'
let doWork = function (config, {data = 'test', hasCache = false}) {
return data;
}
let foo = doWork(
'someUrl/test', {
hasCache: false
}
); //foo = 'test'
- The rest paramater is always the last parameter
let doWork = function (name, ...numbers) {
// numbers is empty array by default
let sum = 0;
numbers.forEach(function(n) {
sum += n;
});
return sum;
};
let foo = doWork('John', 1, 2, 3, 4, 5); // foo = 15
let bar = doWork('John'); // bar = 0;
let doWork = function (x, y, z) {
return x + y + z;
}
let foo = doWork(...[1, 2, 3]); // foo = 6
let foo = [2, 3, 4, 5];
let bar = [1, ...foo, 6, 7, 8, 9, 10]; // bar = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- Use ` (backtick) symbol
let foo = 'John';
let bar = `Hello, my name is ${foo}.
This is new line.`;
- We can call function infront of template literal or a.k.a tagged template literals
let doWork = function (string, ...args) {
// string = ["", " + ", " = ", ". Neat."]
// args = [1, 2, 3]
let result = '';
for (let i = 0; i < string.length; i += 1) {
result += string[i];
if (i < args.length) {
result += args[i];
}
}
return result.toUpperCase();
};
let x = 1;
let y = 2;
let foo = doWork `${x} + ${y} = ${x + y}. Neat.`; // foo = '1 + 2 = 3. NEAT.'
class User {
doWork() {
return 'test';
}
sayHi() {
return 'Hi';
}
}
let foo = new User();
foo.doWork(); // test
foo.sayHi(); // Hi
- We don't need to use commas and the semi-colons are optional.
class User {
constructor(name = 'John Doe') {
this.name = name;
}
sayHi() {
return `Hi ${this.name}.`; // We are using ` (backtick symbol)
}
}
let foo = new User('Jane');
foo.sayHi(); // Hi Jane.
- Useful to add logic when get and set are executed.
class User {
constructor(name) {
this.name = name; // Here we are actualy calling the set function bellow
}
get name() {
return this._name;
}
set name(value) {
if (!value || value.length < 3) {
this._name = 'John Doe';
} else {
this._name = value;
}
}
}
let foo = new User();
foo.name = '';
foo.name; // John Doe
- This keyword allows us to create inheritance between different classes
class Animal {
// ...
}
class Cat extends Animal {
purr() {
console.log('Purrr purr purrr');
}
}
let foo = new Cat();
foo.purr(); // Purrr purr purrr
- Animal is the super class for Cat.
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name, breed) {
super(name); // <------- Calling the Animal constructor
this.breed = breed;
}
}
let foo = new Cat('Kity', 'Persian cat');
foo.name; // Kity
foo.breed; // Persian cat
class Animal {
constructor(name) {
this.name = name;
}
sayHi() {
return 'Hi.';
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
sayHi() {
return super.sayHi() + ' Meow.';
}
toString() {
// We can also override Object functions like .toString(),
// because Animal actualy extends Object class.
return 'I\'m cat.';
}
}
let foo = new Animal('Dogy');
foo.sayHi(); // Hi.
let bar = new Cat('Kity');
bar.sayHi(); // Hi. Meow.
bar.toString(); // I'm cat
let foo = (a, b) => a * b;
foo(2, 5); // 10
// We can write multiple lines with arrow functions,
// but then we must write the return statement
let foo = [1, 2, 3, 4, 5];
let bar = foo.filter(x => {
return x > 3;
}); // [4, 5]
-
Arrow functions adopt the
this
binding from the enclosing (function or global) scope -
Discard the need to create
var self = this;
variable and use it where thethis
binding is different than the one we need
-
If an object holds a collection that is
iterable
, we can use itsiterator
and walk through the collection one item at a time -
Every
iterator
has a.next()
method. That method returns an object every time it's called -
For
.values()
,.entires()
and[Symbol.iterator]
methods check for browser compatibility here
let foo = [1, 2, 3];
// let iterator = foo.values(); // One way
// let iterator = foo.entries(); // Another way
let iterator = foo[Symbol.iterator](); // Another way
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}
- The
iterators
are so calledlazy
and they can do the least amount of work possible. That means that our code might run fast in some scenarios
- As we know the
for in
loop works by enumerating the properties of an object. Thefor of
loop, on the other hand, will enumerate over the values of an object
let foo = ['a', 'b', 'c', 'd'];
for (let n of foo) console.log(n);
// a, b, c, d
- We can create generators with
*
symbol after the function keyword
let foo = function*() {
yield 1;
yield 2;
yield 3;
};
let bar = foo();
bar.next(); // { value: 1, done: false }
bar.next(); // { value: 2, done: false }
bar.next(); // { value: 3, done: false }
bar.next(); // { value: undefined, done: true }
- One of the big advantages of generators is that we can implement a logic when they stop, this way they won't iterate over the entire collection
let foo = function*(items, number) {
let count = 0;
if (number < 1) return; // This will set the done flag to true and the iterator will stop
for (let item of items) {
yield item;
count += 1;
if (count >= number) return; // This will also set the done flag to true
}
};
let bar = [1, 2, 3, 4, 5];
let baz = foo(bar, 2);
for (let n of baz) {
console.log(n);
}
// 1
// 2
- One of the special behavior of generators is that we can pass an argument to the
.next()
method
let range = function*(start, end) {
let current = start;
while (current <= end) {
let passedParameter = yield current;
current += passedParameter || 1;
}
};
let foo = range(1, 10);
foo.next(); // { value: 1, done: false } - cannot be passed a value to the initial next() method
foo.next(2); // { value: 3, done: false }
foo.next(5); // { value: 8, done: false }
- Array comprehension (uses
[...]
)
let foo = [1, 2, 3];
let bar = [for (n of foo) if (n > 1) n * n];
bar; // [4, 9]
- Generator comprehension (uses
(...)
)
let foo = [1, 2, 3];
let bar = (for (n of foo) if (n > 1) n * n);
Array.from(bar); // [4, 9]
- Octal numbers
let octal_1 = 071; // 57
let octal_2 = 0o71; // 57
let nOctal = Number("0o71"); // 57
- Binary
let bin = 0b1101; // 13
let nBin = Number("0b1101"); // 13
-
Number.parseInt();
-
Number.parseFloat();
-
Number.isFinite()
Global isFinite() first converts to number (with Number()) and then executes the logic.
Number.isFinite() doesn't convert to number.
- Number.isNaN()
Global isNaN() first converts to number (with Number()) and then executes the logic.
Number.isNaN() doesn't convert to number.
-
Number.isInteger()
-
Number.MAX_SAFE_INTEGER
JavaScript can deal with numbers correctly in the range of Number.MAX_SAFE_INTEGER
* -1 to Number.MAX_SAFE_INTEGER
- Number.isSafeInteger()
Number.isSafeInteger(9007199254740991); // true
Number.isSafeInteger(9007199254740992); // false
-
Math.acosh(), Math.asinh(), Math.atanh(), Math.cosh(), Math.sinh(), Math.tanh()
-
Math.cbrt(), Math.clz32(), Math.log1p(), Math.log10(), Math.log2(), Math.expm1(), Math.hypot(), Math.fround()
Math.log10(100); // 2;
Math.log2(16); // 4;
Math.hypot(3,4); // 5, hypot refer to hypotenuse
Math.fround(2.888); //2.888000011444092
- Math.sign(), Math.trunc()
Math.sign(572); // 1;
Math.sign(0); // 0;
Math.sign(-83); // -1;
Math.trunc(2.8); //2 - Truncate the ingeter from a number
Math.trunc(-2.8); // -2;
- .find(fn())
let foo = [1,2,3];
let bar = foo.find(x => x > 2); // 3
- .findIndex(fn())
let foo = [1,2,3];
let bar = foo.findIndex(x => x > 2); // 3 - finds the first match and take its index
- .fill(item, startIndex, endIndex)
let foo = [1,2,3];
let bar = foo.fill('a'); // ['a,'a,'a]
- .copyWithin(target, start, end)
let foo = [1,2,3,4];
let bar = foo.copyWithin(2, 0); // [1,2,1,2]
let baz = foo.copyWithin(0, -2); // [3,4,3,4]
- Array.of()
let foo = new Array(3); // [,,]
let bar = Array.of(3); // [3]
- Array.from()
let foo = document.querySelectorAll('div'); // Doesn't have .forEach
let bar = Array.from(foo); // Have .forEach
- .entries()
let foo = ['Joe', 'Jack', 'Jim']
let bar = foo.entries();
bar.next(); // {value: [0, 'Joe'], done: false}
bar.next(); // {value: [1, 'Jack'], done: false}
bar.next(); // {value: [2, 'Jim'], done: false}
bar.next(); // {value: undefined, done: true}
- .keys()
let foo = ['Joe', 'Jack', 'Jim']
let bar = foo.keys();
bar.next(); // {value: 0, done: false}
bar.next(); // {value: 1, done: false}
bar.next(); // {value: 2, done: false}
bar.next(); // {value: undefined, done: true}
- Array comperhensions
let foo = [for (i of [1,2,3]) i]; // [1,2,3]
let bar = [for (i of [1,2,3]) i*i]; // [1,4,9]
let baz = [for (i of [1,2,3]) if (i < 3) i]; // [1,2]
let x = [for (first of ['Willian', 'John', 'Blake'])
for (middle of ['Robert', 'Andrew', 'John'])
if (first !== middle) (first + ' ' + middle + 'Smith')]
// x = ["Willian RobertSmith", "Willian AndrewSmith",
"Willian JohnSmith", "John RobertSmith",
"John AndrewSmith", "Blake RobertSmith",
"Blake AndrewSmith", "Blake JohnSmith"]
-
Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
-
.size, .add(), .has(), .clear(), .delete(), .forEach(), .entries(), .values()
let set = new Set();
set.size; // 0
set.add(1);
set.size; // 1
let objectValue = {};
set.add(objectValue);
set.delete(objectValue);
set.clear();
set.add('a');
let e = set.entries();
e.next(); // {value: [0, 'a'], done: false}
e.next(); // {value: undefined, done: true}
let s2 = new Set(set.values()); // Set {'a'}
let s3 = new Set(s2); // Set {'a'}
-
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
-
.size, .set(), .get(), .has(), .clear(), .delete(), .entries(), .values(), .keys(), .forEach(function(value, key) {})
let map = new Map();
map.set("age", 28);
map.get("age"); // 28
let foo = {id: 1};
map.set(foo, 42);
map.get(foo); // 42
let map = new Map([['name', 'John'], ['age', 28], ['weight', 70]]);
- If duplicate key is added, it will replace the old value
let map = new Map();
let key = {};
map.set(key, 1);
map.set(key, 2);
map.get(key); // 2
let map = new Map([['name', 'John'], ['age', 28], ['weight', 70]]);
for (let item of map) {
// item is an array like ['name', 'John']
}
for (let [key, value] of map) {
// use key and value letiables
}
The pointers in the WeakMap & WeakSet are not strong pointers, therefore their values can be removed from the garbage collector (if the values are not in use anywhere else).
WeakSet don't have: .size, .entries, .values and .forEach, for (let key of myWeakSet) WeakSet have: .has(), .delete(), .clear()
WeakMap don't have: .size, .entries, .keys, .values and .forEach, for (let key of myWeakMap) WeakMap have: .has(), .get(), .delete(), .clear()
let promise = new Promise(function(resolve, reject) {
resolve('foo');
});
promise.then(function(data) {
// success
console.log(data); // foo
}, function(error) {
// reject
console.log(error); // bar
});
let firstPromise = new Promise(function(resolve, reject) {
resolve('foo');
});
let secondPromise = Promise.resolve(function(resolve, reject) {
resolve(firstPromise);
});
// let secondPromise = Promise.resolve(firstPromise);
// let secondPromise = Promise.reject(firstPromise);
secondPromise.then(function(data) {
// success
console.log(data); // foo
}, function(error) {
// reject
console.log(error); // bar
});
function getDataA (id) {
return Promise.resolve({id: id});
}
function getDataB (name) {
return Promise.resolve({name: name});
}
function getDataC (age) {
return Promise.resolve({age: age});
}
getDataA(42).then((data) => {
console.log(data.id);
return getDataB('foo');
})
.then((data) => {
console.log(data.name);
return getDataC(25);
})
.then((data) => {
console.log(data.age);
})
.catch((error) => {
console.log(error);
});
// Wait all promises to be resolved
let numbers = [1, 2, 3];
let queue = [];
function getDataA (id) {
return Promise.resolve({id: id});
}
for (let i = 0; i < numbers.length; i++) {
queue.push(getDataA(numbers[i]));
}
Promise.all(queue).then((data) => {
console.log(data); // [{id: 1}, {id: 2}, {id: 3}]
});
// Resolve after the first promise
let numbers = [1, 2, 3];
let queue = [];
function getDataA (id) {
return Promise.resolve({id: id});
}
for (let i = 0; i < numbers.length; i++) {
queue.push(getDataA(numbers[i]));
}
Promise.race(queue).then((data) => {
console.log(data);
// {id: ???} - we dont' know the id because the order is unknown
});
TODO:
The Object.is() method determines whether two values are the same value. (===)
Edge cases:
-0 === 0; // true
Object.is(-0, 0); // false
NaN === NaN; // false
Object.is(NaN, NaN); // true
- The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
var foo = {a: 1};
var bar = Object.assign({}, foo);
console.log(bar); // {a: 1}
For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value - JSON.parse(JSON.stringify());
Merging objects:
var foo = {a: 1};
var bar = {b: 2};
var baz = {c: 3};
Object.assign(foo, bar, baz);
console.log(foo); // {a: 1, b: 2, c: 3}
Merging objects with same properties - the properties are overwritten by other objects that have the same properties later in the parameters order.
var foo = {a: 1, b: 1, c: 1};
var bar = {b: 2, c: 2};
var baz = {c: 3};
var xyz = Object.assign({}, foo, bar, baz);
console.log(xyz); // {a: 1, b: 2, c: 3}
- Object shorthand
let model = 'Ford';
let year = 1969;
let classic = {
model: model,
year: year
};
let modern = {
model,
year
};
console.log(classic);
console.log(modern);
- Computed property names
// EC5
function createSimpleObject (propName, propVal) {
let foo = {};
foo[propName] = propVal;
return foo;
}
// EC6
function createSimpleObject (propName, propVal) {
return {
[propName]: propVal
};
}
The brackets [...] mean that the value inside is expression and should be evaluated.
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
// Intercepting get and set requests
let foo = {
age: 28,
name: 'Foo'
};
let handler = {
get: function(target, prop) {
if (prop === 'age') {
return 'Awesome ' + target[prop];
} else {
return target[prop];
}
},
set: function(target, prop, value) {
if (prop === 'name' && value.length < target[prop].length) {
console.log('Name must be longer than the current value.');
} else {
target[prop] = value;
}
},
delete: ...,
define: ...,
freeze: ...,
seal: ...,
hasOwnProperty: ...
};
let fooProxy = new Proxy(foo, handler);
fooProxy.age; // "Awesome 28"
fooProxy.name = 'F'; // Name must be longer than the current value.
fooProxy.name; // Foo
- Proxying functions
let foo = {
age: 28,
name: 'Foo',
attack: function(target) {
return target.name + ' was obliterated!';
}
};
foo.attack = new Proxy(foo.attack, {
// The original foo.attack is no longer accessible
apply: function(target, context, args) {
// If the context is not foo, we'll return a warning that noone except foo can use the attack function.
if (context !== foo) {
return 'The attack function can be used only by foo.';
} else {
return target.apply(context, args);
}
}
});
let bar = {name: 'Bar'};
bar.attack = foo.attack;
bar.attack(foo); // The attack function can be used only by foo.
foo.attack(bar); // Bar was obliterated!
Currently (January, 2017), no browser supports the import and export keywords.
- Import/Export one by one
// foo.js
export class Foo {
doWork {
return 'Foo is working.';
}
}
export let goThere = function() {
console.log('Go there.');
};
export let MAX_LENGTH = 5;
// bar.js
import {Foo, goThere, MAX_LENGTH} from './foo.js';
let f = new Foo();
f.doWork(); // Foo is working.
goThere(); // Go there.
console.log(MAX_LENGTH); // 5
- Import the whole module
// foo.js
export class Foo {
doWork {
return 'Foo is working.';
}
}
export let goThere = function() {
console.log('Go there.');
};
export let MAX_LENGTH = 5;
// bar.js
module foo from './foo.js';
let f = foo.Foo();
f.doWork(); // Foo is working.
foo.goThere(); // Go there.
console.log(foo.MAX_LENGTH); // 5
Named exports are useful to export several values. During the import, one will be able to use the same name to refer to the corresponding value. Concerning the default export, there is only a single default export per module. A default export can be a function, a class, an object or anything else. This value is to be considered as the "main" exported value since it will be the simplest to import.
// foo.js
export default class Foo {
doWork {
return 'Foo is working.';
}
}
// bar.js
import customFooName from './foo.js';
let f = new customFooName();
f.doWork(); // Foo is working.
// foo.js
export default class Foo {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
doWork {
return 'Foo is working.';
}
}
// bar.js
import Foo from './foo.js';
let f = new Foo('Foo');
f._name = 'Bar';
f.doWork(); // Bar is working