https://github.com/zsolt-nagy/es6-summary
http://www.zsoltnagy.eu/category/es6/
https://hacks.mozilla.org/category/es6-in-depth/
https://github.com/xgqfrms-GitHub/Native-JavaScript/blob/master/es6-emoji.html
https://jsfiddle.net/sybn4h33/3/
https://gist.github.com/xgqfrms-GitHub/19479fdadbbc05356aceaf56073bec6d
A short summary of ES6 features and their ES5 equivalents.
Access in-depth ES6 articles here.
Table of contents
- Arrow Functions
- Functions
- Scope
- Default Argument Values
- Classes
- Object Property Shorthand Notation
- Destructuring
- Rest Parameters
- Spread Operator
- Objects
- Tail call optimization
- Symbols
- Sets
- Maps
- Weak Sets
- Weak Maps
- for...of loop
- Iterable Interface
- Generator functions
- Strings
- Template literals
- Tag functions for Template Literals
- Promises
- Modules
- Reflect API
- Proxies
- 59 Exercises
- describes a mapping between the argument list and return value
- the return value may be in a block (see
2
,3
, and4
) - only use it when implicit context binding is acceptable
- don't use arrow functions in classes, constructors, prototype extensions
// 1.
a => value;
// 2.
a => { return value; }
// 3.
( a, b ) => {
statements;
return value;
}
// 4.
( ...args ) => {
statements;
return value;
}
// 1.
// 2.
function( a ) { return value; }.bind( this );
// 3.
function( a, b ) {
statements;
}.bind( this );
// 4.
function() {
var args = arguments;
statements;
}.bind( this );
const f = () => null;
f.name
> "f"
const C = function() {
console.log( new.target === C );
}
const o = new C();
> true
var
: function scope
(function() {
// first and second are accessible
// first = undefined, second = undefined
var first = 1;
{
var second = 2;
}
// first and second are accessible
// first = 1, second = 2
});
After hoisting:
(function() {
var first, second;
first = 1;
{
second = 2;
}
// first and second are accessible
// first = 1, second = 2
});
let
,const
: block scope
let
and const
declarations are hoisted just like vars. However, their values are not accessible before their first assignment.
{
// accessing radius or PI throws an error
const PI = 3.14;
let radius = 2;
// radius and PI are accessible
// radius may be modified
}
// accessing radius or PI throws an error
ES6:
function( name, phone = '-' ) {
// ...
}
ES5:
function( name, phone ) {
phone = typeof phone === 'undefined' ? '-' : phone;
// ...
}
Warning: default arguments does not affect the arguments
array
const f = ( x = 0, y = 0 ) => {
console.log( arguments.length );
};
f( 0 )
> 1
ES6:
- concise method syntax has to be used
class Shape {
constructor( color ) {
this.color = color;
}
getColor() {
return this.color;
}
}
ES5:
function Shape( color ) {
this.color = color;
}
Shape.prototype.getColor = function() {*
return this.color;
}
Rectangle
is the child class,Shape
is the parent class- redefining a method with the same name in the child class shadows the method in the parent class
super
has to be called in the constructor in the child class- in the absence of a constructor, a constructor including a
super
call is implicitly assumed
ES6:
class Rectangle extends Shape {
constructor( color, width, height ) {
super( color );
this.width = width;
this.height = height;
}
}
ES5:
function Rectangle( color, width, height ) {
Shape.call( this, color );
this.width = width;
this.height = height;
}
Rectangle.prototype = Object.create( Shape.prototype );
Rectangle.prototype.constructor = Rectangle;
class Rectangle extends Shape {
constructor( color, width, height ) {
super( color );
this.width = width;
this.height = height;
}
get area() {
return this.width * this.height;
}
set area( value ) {
console.log( 'Attempting to set area to ' + value );
throw 'Setting area is not allowed';
}
}
const myRectangle = new Rectangle( 'red', 5, 3 );
myRectangle.area
> 15
myRectangle.area = 25
> Attempting to set area to 25
- class level methods
- cannot be accessed from instances
class Die {
static cast() { return Math.round( Math.random() * 6 ) + 1; }
}
Die.cast()
> 3
ES6:
var language = 'Markdown';
var extension = 'md';
var file = {
language,
extension
};
ES5:
var language = 'Markdown';
var extension = 'md';
var file = {
language: language,
extension: extension
};
null
orundefined
on the right hand side of a destructuring expression yields a JavaScript error
ES6:
var a = 1, b = 2;
// 1.
[a, b] = [b, a + b];
// 2.
var [,c] = [a, b];
// 3.
var [,,d] = [1, 2];
ES5:
var a = 1, b = 2;
// 1.
var temp_a = a, temp_b = b;
a = temp_b;
b = temp_a + temp_b;
// 2.
var temp_b2 = b;
var c = temp_b2;
// 3.
var d = undefined;
ES6:
var file = {
filename: 'es6.md',
metadata: {
language: 'Markdown',
extension: 'md'
}
};
var { filename, metadata: { language } } = file;
// same as:
// var { filename, metadata: { language: language } } = file;
ES5:
var file = {
filename: 'es6.md',
metadata: {
language: 'Markdown',
extension: 'md'
}
};
var temp_filename = file.filename;
var temp_language = file.metadata.language;
var filename = temp_filename;
var language = temp_language;
LEFT = RIGHT
returnsRIGHT
{ a } = { b } = { a: 1, b: 2 }
// is evaluated as:
// b becomes 2
{ a } = { a: 1, b: 2 }
// a becomes 1
{ a: 1, b: 2 }
function f( L1, L2 ) { ... }
f( R1, R2 );
executes
L1 = R1;
L2 = R2;
ES6:
( function( first, ...rest ) {
console.log( 'first:', first );
console.log( 'rest:', rest );
} )( 1, 'Second', 3 );
> first: 1
> rest: ['Second', 3]
ES5:
- note:
arguments
is not an array in JavaScript, it has to be converted to an array withArray.from
to be able toslice
it
( function() {
var first = arguments[0];
var rest = Array.from( arguments ).slice( 1 );
console.log( 'first:', first );
console.log( 'rest:', rest );
} )( 1, 'Second', 3 );
> first: 1
> rest: ["Second", 3]
const arr = [1, 2, 3];
// ...arr spreads the elements of arr into comma separated values
[ ...arr, ...arr ]
> [1, 2, 3, 1, 2, 3]
Math.max( ...arr ) // same as Math.max( 1, 2, 3 )
> 3
- Strings are spread as comma separated strings of one character each.
[...'ES6']
> ["E", "S", "6"]
- ES5 equivalents: loops, utility functions,
call
/apply
function for calling functions with a variable number of arguments
ES6:
Object.assign( target, ...sourceList )
- copies all key-value pairs of
sourceList
totarget
, mutatingtarget
- copying is done from left to right
- returns target
Example:
var target = { a: 1 };
Object.assign( target, { b: 1}, { a: 2, b: 2 } );
// target becomes { a: 2, b: 2} AND returns target
ES5:
- assume all arguments are objects, and they are not null
- in practice, error handling should be taken care of, this implementation is for illustration purposes only
Object.assign = function( target /*, ...sourceList */ ) {
var source;
for ( var i = 1; i < arguments.length; ++i ) {
source = arguments[i];
for ( var key in source ) {
if ( source.hasOwnProperty( key ) ) target[key] = source[key];
}
}
- Place any JavaScript expression as an object key between
[
and]
- The JavaScript expression is stringified and converted into an object key
{
[ 3 + 2]: 1,
[ [] ]: 2
}
becomes
{
"5": 1,
"[object Object]": 2
}
const publicInterface = {
operation() { return 'ES6'; }
}
Object.getPrototypeOf( o );
Object.setPrototypeOf( o, newProto );
- Tail call optimization avoids using the stack for a function call
- Function calls in tail position are tail-call optimized
- A function is in tail position if it's the last action in the function
- Compatibility: currently low support ( https://kangax.github.io/compat-table/es6/ )
- 尾调用优化避免了使用堆栈进行函数调用
Regular recursion:
function sumToN( n ) {
if ( n <= 1 ) return n;
return n + sumToN( n - 1 );
};
尾调用优化使用 accumulator variables:
function sumToN( n, sum = 0 ) {
if ( n <= 1 ) return sum;
let result = sum + n;
return sumToN( n - 1, result );
};
Symbol()
creates a unique symbol- Objects can have string or symbol keys
- Node.js does not print Symbol keys
JSON.stringify
excludes Symbol keysObject.assign
copies Symbol keys over totarget
const s1 = Symbol();
const s2 = Symbol();
s1 === s2
> false
typeof s1
> "symbol"
const o = {
[s2]: 1,
[Symbol()]: 2
};
o[s1] = 3;
JSON.stringify( o )
> "{}"
const s1 = Symbol.for( 'key' );
const s2 = Symbol.for( 'key' );
s1 === s2
> true
Symbol.keyFor( s1 )
> "key"
- arbitrary values
- values can be enumerated using the iterable interface
let colors = new Set();
colors.add( 'red' );
colors.add( 'green' );
colors.size
> 2
colors.has( 'green' )
> true
colors.delete( 'green' )
> true
- arbitrary keys
- arbitrary values
- keys can be enumerated using the iterable interface
let horses = new Map();
horses.set( 8, 'Chocolate' );
horses.size
> 1
horses.has( 8 )
> true
horses.get( 8 )
> 'Chocolate'
horses.delete( 8 )
> true
Alternative map constructor
let horses = new Map( [[8, 'Chocolate'][3, 'Filippone]] );
- sets hold weak reference to their values: if the only reference to a value is in the set, the value is garbage collected
- size of the set is unknown
- weak sets may only store objects
- weak sets are not iterable
let firstElement = { order: 1 }, secondElement = { order: 2 };
let ws = new WeakSet( [ firstElement, secondElement ] );
ws.has( firstElement )
> true
firstElement = {};
ws.has( firstElement )
> false
- object keys, arbitrary values
- only the keys of the weak map are weak: in case the map holds the only reference to its key, the key is garbage collected
let firstElement = { order: 1 }, secondElement = { order: 2 };
let wm = new WeakMap();
wm.set( firstElement, 1 );
wm.set( secondElement, {} );
wm.get( secondElement )
> {}
secondElement = {};
wm.get( secondElement )
> undefined
- works on iterables
const arr = [1, 2, 3];
const message = 'hello';
for ( let i of arr ) console.log( i );
> 1
> 2
> 3
for ( let ch of message ) {
console.log( ch );
}
> 'h'
> 'e'
> 'l'
> 'l'
> 'o'
let colors = new Set();
colors.add( 'red' );
colors.add( 'green' );
for ( let color of colors ) console.log( color );
> 'red'
> 'green'
let horses = new Map( [[8, 'Chocolate'][3, 'Filippone]] );
for ( let [key, value] of horses ) console.log( key, value );
> 8 'Chocolate'
> 3 'Filippone'
- Iterable object: has a
[Symbol.iterator]
method returning an iterator object - Iterator object: have a
next()
method returning{ done: <Boolean>, value: <NextValue> }
of the iteration - when
done
is truthy, the iteration ends - when
done
is falsy,value
is the upcoming element of the iteration
for...of
loop consumes iterables...
(spread) operator consumes iterables
- Arrays
- Strings
- DOM data structures
- Maps
- Sets
Example:
let colors = new Set( [ 'red', 'yellow', 'green' ] );
let horses = new Map( [ [5, 'QuickBucks'], [8, 'Chocolate'], [3, 'Filippone'] ] );
console.log( colors.entries() );
> SetIterator {["red", "red"], ["yellow", "yellow"], ["green", "green"]}
console.log( colors.keys() );
> SetIterator {"red", "yellow", "green"}
console.log( colors.values() );
> SetIterator {"red", "yellow", "green"}
console.log( horses.entries() );
> MapIterator {[5, "QuickBucks"], [8, "Chocolate"], [3, "Filippone"]}
console.log( horses.keys() );
> MapIterator {5, 8, 3}
console.log( horses.values() );
> MapIterator {"QuickBucks", "Chocolate", "Filippone"}
- Return an iterable that is also an iterator
function*
keyword creates a generator functionyield
keyword yields the next value returned by the iterator, withdone: false
return v
exits the generator with{ done: true, value: v }
.v
is not consumed by data consumers
function *getLampIterator() {
yield 'red';
yield 'green';
return 'lastValue';
}
let lampIterator = getLampIterator();
console.log( lampIterator.next() );
> Object {value: "red", done: false}
console.log( lampIterator.next() );
> Object {value: "green", done: false}
console.log( lampIterator.next() );
> Object {value: "lastValue", done: true}
let generator1 = function *() { ... };
let generator2 = function *() { ... };
let combinedGenerator = function *() {
yield *generator1();
yield *generator2();
}
let greetings = function *() {
let name = yield 'Hi!';
yield `Hello, ${ name }!`;
}
let greetingIterator = greetings();
console.log( greetingIterator.next() );
> Object {value: "Hi!", done: false}
console.log( greetingIterator.next( 'Lewis' ) );
> Object {value: "Hello, Lewis!", done: false}
const s = 'hello'
s.startsWith( 'he' );
> true
s.endsWith( 'lo' );
> true
s.includes( 'll' );
> true
s.repeat( 3 );
> 'hellohellohello'
// codePointAt returns the character code regardless of whether it's a 2, 3, or 4 byte character
'\u{1f600}\u{1f600}'.codePointAt( 0 );
> 128512
'\u{1f600}\u{1f600}'.codePointAt( 1 ); // garbage: second half of the first character
> 56832
'\u{1f600}\u{1f600}'.codePointAt( 2 );
> 128512
- evaluate JavaScript expressions inside
${
and}
- can span multiple lines
- return a string
const x = '3 + 2';
console.log( `${x} is ${3 + 2}
.
.
.` );
> 3 + 2 is 5
> .
> .
> .
tagFunction( literalFragmentArray, ...expressionSubstitutions ) {
// returns string returned by the template literal
}
Example:
const toUpperExpressionsTag = ( literals, ...expressions ) => {
let str = '';
for ( let i = 0; i < expressions.length; ++i ) {
str += literals[i] + expressions[i].toUpperCase();
}
str += literals[ literals.length - 1];
return str;
}
const dave = 'dave';
toUpperExpressionsTag`Hello ${ dave }!`
> "Hello DAVE!"
- promises are immutable
- they are either kept or broken
- when a promise is kept, we are guaranteed to receive a value
- when a promise is broken, we are guaranteed to receive a reason (error)
pending
: may transition tofulfilled
orrejected
fulfilled
: must have a valuerejected
: must have a reason for rejection
let promise = new Promise( function( resolve, reject ) {
// call function resolve( value ) to resolve a promise
// call function reject( reason ) to reject a promise
} );
let onFulfilled = function() { /* success handler */ }
let onRejected = function() { /* rejection handler */ }
promise.then( onFulfilled, onRejected );
Then is chainable: it returns a promise
promise.then( s1, r1 ).then( s2, r2 );
- On success, it returns
s2( s1( v1 ) )
in casepromise
is resolved withv1
- If
promise
is resolved with return valuev1
, ands1( v1 )
returns a value, then the promise callings2
will be fulfilled with values1( v1 )
Errors can be caught with promise.catch()
promise.then( onFulfilled1 ).then( onFulfilled2 ).catch( onReject );
let promise = Promise.resolve( v );
p.then( ( value ) => console.log( 'Value:', value ) )
.then( () => { throw new Error('Error in second handler' ) } )
.catch( ( error ) => console.log( 'Error: ', error.toString() ) );
- handles an iterable of promises
onFulfilled
is called once all promises in the iterable are resolvedonRejected
is called once any promise in the iterable is rejected
Promise.all( [ promise1, promise2 ] ).then( onFulfilled, onRejected );
- Importing module from
./moduleName.js
and./folder/moduleName2.js
import module from 'moduleName';
import { key1, key2 } from 'folder/moduleName2'; // notice the destructuring on the left
- Exporting objects in
./moduleName.js
:
class C { ... }
export default C;
The Reflect API is far too complex to be summarized in a couple of paragraphs. It is also an advanced topic, requiring in-depth understanding. For this reason, I highly recommend the below article.
Proxies are not only complex, but they heavily build on the Reflect API. Check out this article to learn them.
Check out ES6 in Practice, and put theory into practice by solving 59 exercises, and checking out their reference solutions.