An introduction to help ramp you up quickly with ES6
features, tools and syntax - there's even a short section on ES7
.
Please read the contributions section and check back from time to time for intermittent updates.
- ECMA Brief History
- Introduction
- Tools
- Block Scoping
- Let and Const
- Destructuring
- String & Template Literals
- Math & Number
- Arrays
- Parameters
- Modules
- Getters & Setters
- Classes
- Symbols
- Generators
- Arrow Functions
- Map & WeakMap
- Set & WeakSet
- Promises
- Contributions
- ES7 - ES2016 ⭐
- ECMAScript is now 17 years old with it's birth in 1997
- ECMAScript is the standard.
JavaScript
,ActionScript
andJScript
are implementations of it - JavaScript popularized two paradigms on the web,
functional programming
andprototypal inheritance
(objects without classes) JavaScript
first appeared in Navigator 2.0 browser and has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0- JavaScript was designed with C-like syntax with curly braces, the statement/expression dichotmory, dot notation etc
- ECMAScript is designed by TC39 and operates on consensus
- ECMAScript is the standard.
A brief snapshot of it's history:
Edition | Year | Notes |
---|---|---|
1 | 1997 | Oracle (prev. Sun) had trademark to Java, therefore JavaScript |
2 | 1998 | ECMA International creates the ECMA-262 standard |
3 | 1999 | Introduced many features inherint in the language today |
*3.1 | 2008 | Eventually standardized as the 5th of ECMA-262, also described as ECMAScript 5 |
*4 | abandoned | Abandoned due to political reasons |
5 | 2009 | Added 'use strict' ( aligns with 3+ ) |
5.1 | 2011 | Maintenance revision (alings with 3+ ) |
6 | 2015 | Significant features added, that's why you're here |
7 | 2016 | Released, seventh edition of ECMAScript |
8 | 2017 | Released, eighth edition of ECMAScript |
ES6
is the newest addition to the language and adds significant new syntax for writing complex JavaScript applications.
- There is 5 years between
ES5
andES6
. - 'ES6' is the first ECMAScript Harmony specification and is also known as
ES Harmony
,ES2015
,ES.next
but commonly referred to asES6
. - The specification was finalized in
June 2015
as it reachedfeature complete
status- Subsequent versions will be released on a 12 month cadence, and will be dubbed
ES7
,ES2016
respectively.
- Subsequent versions will be released on a 12 month cadence, and will be dubbed
You should want to experiment and play around with code — it's easy to get up and running. There are several techniques and the premise all these tools are built upon is called transpiling
, or JavaScript to JavaScript transpiling which compiles the latest version into older versions.
- Transpilation is the future for future ECMAScript such as
ES2015
andES2016
- The most popular JavaScript transpilers are
Babel
and was previously known as6to5
andtraceur
- Transpilation is possible to incorporate into your build process using Broccoli, Gulp, Browserify, RequireJS, Webpack et al and friends.
The easiest way to get up and running in the web browser
:
- In the web browser you can use the online
Babel REPL
— this compilesES6
toES5
and you don't have to install anything. - Another web based tool is
Scratch JS
and comes in the form of a chrome extension. This interactive playground let's you transpile your code in the all familiardev tools
environment.
If you prefer the command line
:
- Use Node,js v4.x.x or
>
, they support is in-built for Babel - Run
npm install -g babel' and
babel node`
Using an IDE
:
- You can easily use
Babel
to transpile your code in Webstorm, the setup won't take more than a couple of moments.
Node has decent built in support for ES6
thanks for the V8
engine:
- Node suports
ES6
features split into three groups - Shipping: which do not require a runtime flag
- Staged: almost-complete features that are not considered stable
- In progress: features can be individually activated by their respective harmony flag
--harmony_destrucuting
ES6 introduces block scoping:
- anything between
{
...}
introduces it's own scope - this works like an
IIFE
- functions are block scoped in ES6
- this is more mainstream than function scoping which makes it easier when coming from or moving to other languages
{
let quux = "Hello World from ES6";
console.log(quux); // => Hello World from ES6
}
{
let foo = "This has a different scope";
console.log(foo); // => This has a different scope
}
console.log(quux); // Reference Error: quux is not defined
In ES5 you would use the
function
as the fundamental construct for all your variable scoping.
var x = 7;
(function iife() {
var x = 10;
console.log( x ); // => 10;
})();
console.log( x ); // 7
let
and const
are two new identifiers for storing values in ES6
which you can additionally use to declare variables
'let' and 'const' throw more exceptions as they behave more strictly than 'var'
let
is block scoped and is hoisted to the top of it's block, unlikevar
which is hoisted to the top of it's function or scriptconst
is also block scoped and is also hoisted to the top of it's block- it's worth noting that in ES6 functions are block scoped instead of lexically scoped 'var`
let
does not create a property on the global objectconst
creates immutable variables, whilstlet
create mutable ones- a duplicate declaration of
let
will throw a Reference Error within a block or function - this is also known as
TDZ
or temporal dead zone - Basic rules to follow:
- don't use
var
, or leave it as a signal in untouched legacy code. - use
let
when you want to rebind. - prefer
const
for variables that never change. - don't blindly refactor legacy code when replacing
var
withlet
andconst
.
How let
works:
function myfunc() {
if ( true ) {
let x = 54321;
}
console.log( x ); // => ReferenceError: x is not defined
}
// legacy ES5
function myfunc() {
if ( true ) {
var x = 54321;
}
console.log( x ); // => 54321
}
Using block scoped let
:
let outer = "outer";
{
let inner = "inner";
{
let nested = "nested"
}
console.log( inner ); // => you can access `inner`
console.log( nested ) // => throws error
}
// you can access `outer` here
// you cannot access `inner` and `nested` here
More scoping rules for variables declared by let
. you can clearly see that when refactoring:
function letBlocks {
let a = 23;
if ( true ) {
let a = 56;
console.log( a ); // => 56
}
console.log( a ); // => 23
}
How const
works. Variables declared with const
are immutable, but note that the values aren't just the declarations:
const foo = '123';
foo = '321';
console.log( foo ); // TypeError
// changing const values
const myarr = [1,2,3,4];
myarr.push(5);
console.log( myarr ); // => 1, 2, 3, 4, 5
const obj = {};
obj.prop = 123;
console.log( obj ); // => { Object: prop: 123 }
Just like an object literal is a convenient way to construct an object, ES6 destructing allows you to extract values from data stored in objects or arrays.
- You can assign to more than just variables
- Destructuring can be used in assignments, variable declarations and parameters
spread
andrest
operators work with destructuring- You can destructure a
for-of
loop
Objects:
const myObj = { firstname: 'Ahad', lastname: 'Bokhari' };
const { firstName: fname, lastName: lname }; = myObj
console.log( fname, lname ); // => Ahad Bokhari
let a, b;
({ a, b } = {a: 1, b: 2});
console.log(a, b);
// deeper objects
const {
prop1: z,
prop2: {
prop2: {
nested: [ , , c]
}
}
} = { prop1: "Hello", prop2: { prop2: { nested: ["a", "b", "c"] }}};
console.log(z, c);
Arrays:
const myArr = [ 'y', 'z'];
const [ a, b ] = myArr;
console.log( a, b ); // => y, z
let [ a, , b ] = [ 'x', 'y', 'z' ];
console.log( a, b );
// deeper arrays
const [ a, [b, [c, d]]] = [1, [2, [[[3, 4], 5], 6 ]]];
console.log("a:", a, "b:", b, "c:", c, "d:", d); // => a: 1 b: 2 c: [ [ 3, 4 ], 5 ] d: 6
We all know that JavaScript strings
are limited and lacking in capabality, especially if you're coming from Ruby or Python. Template literals are a feature that developers will love and are basically just string literals
allowing embedded expressions
.
ES6
introducesstring interpolation
,string formatting
,multiline strings
andembedded expressions
with template literals- Template strings use (``) rather than single or double quotes used with regular strings
- Placeholders using the
${ }
syntax are used from string substition and works fine with any kind of expression - expressions in between the placeholders (${expression}) and text b/w them get passed to a function
Familiarizing yourself with the syntax
:
const a = `this is a template literal`;
console.log( typeof a ); // => string
const b = `You can use template literals in multiline
statements without using \\n`;
console.log( b ); // => yup, it works!
const c = `Some string text ${expression}`;
Use the following syntax to embed
expressions within template literals
const a = 100,
b = 100;
console.log(`The sum of ${a} * ${b} is ${a * b}`);
// => The sum of 100 * 100 is 1000
const user = { name: `Ahad Bokhari` };
console.log(`You are now logged in, ${ user.name.toUpperCase() }. `);
// => You are now logged in AHAD BOKHARI
- Additions to
integer literals
have been added includedoctal
andbinary
literals - Methods that have been added to
Number
apart from the usual four suspects are,Number.EPSILON
,Number.isInteger
,Number.isNaN
,Number.isFinite
,number.isSafeInteger
Number.MAX_SAFE_INTEGER
,Number.MIN_SAFE_INTEGER
are the largest and smalled integer that are represented in JavaScript- The math object has also received some useful methods in
ES6
,Math.sign
,Math.trunc
,Math.cbrt
ES6
brings an abundance of new Array methods
- Array:
from
,of
- Array.prototype:
findIndex()
,fill()
,find()
,copyWithin()
,keys()
,values()
,entries()
Using Array.from
we create a new array instance from an array-like
or iterable object
var doc = document.querySelectorAll('*');
Array.from(doc).forEach(function(nodes) {
console.log(nodes);
});
Using Array.of
we create an array of variable arguments passed to the function
var arr = Array.of(true, null, undefined, `some message`, 50);
console.log( arr ); //[ true, null, undefined, "some message", 50]
Examples of additional array
methods:
// copyWithin()
[1, 2, 3, 4, 5].copyWithin(0, 3); // => [4, 5, 3, 4, 5]
let letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"];
letters.copyWithin(4, 0); // => there is an optional parameter copyWithin(4, 0, 2)
console.log(letters); // ["A", "B", "C", "D", "A", "B", "C", "D", "E", "F"]
// find()
let cities = ["Beirut", "Karachi", "Islamabad", "Athens", "Kabul", "Tapei", "Bucharest"];
let t = cities.find(char => char.endsWith("t"));
console.log( t );
// keys()
let people = ["Ahad", "Moe", "Zoey", "Snaey"];
for( let entry of fruits.entries() ) {
console.log(entry[0]);
// 0
// 1
// 2
// 3
console.log(entry[1]);
// Ahad
// Moe
// Zoey
// Snaey
}
In ES6 we have a standardized and more laconic way to handle parameters ( /i.e: defaults, parameters and arguments ) which greatly reduces boilerplate code.
In ES5
you could do something like this to handle default parameters
:
function multiply(x, y) {
y = y || 10; // Default to 1
return x * y;
}
It's counterpart in 'ES6':
function multiply(x, y = 10) {
return x*y;
}
multiply(10); // => 100
The rest
parameter has been added into the spec, which also reduces the boilerplate code for handling arguments:
function f(...theArgs) {
console.log(theArgs.length);
}
f1(); // => 0
f1(5); // => 1
f1(5, 6, 7); // => 3
note: the arguments object is not a real array - rest params are instances of
Array
so you can call methods like map, forEach directly
Another example of using rest
, note how we collected arguments into a real array and not an arguments object:
function logEach(...stuff) {
stuff.forEach(function (stuff) {
console.log(stuff);
});
}
logEach("a", "b", "c");
The ...
construct in ES6
is also used to provide “spread” for both function calls and array literals:
// => in `ES5' we'd do this
function fnes5(a, b, c) {
console.log(a, b, c); // 1, 2, 3
}
var args = [1, 2, 3];
fnes5.apply(null, args);
// in `ES6` we avoid the use of `apply`
function fnes6(a, b, c) {
console.log(a, b, c); // 1, 2, 3
}
var args = [1, 2, 3];
fnes6(...args); // => 1, 2, 3
At the core of modularity developers need a module system — a way to spread their work across numerous files and directories with access to each other. Without support for modules natively in ECMAScript there has been a community created effort to implement work-arounds — CommonJS and AMD being the most prevelant as they both have communities that rally around them, but are incompatible with each other. The good news is ES6, ( TC39 group ) has finalized a module syntax which developers can greatly benefit from using the best from both worlds.
- The goal of modules in
ES6
is to keep bothCommonJS
andAMD
user happy with a single format and borrows the best from both worlds ES6
modules will have a compact syntax with a preference for a single exports ( such as CommonJS )- direct support for asynchronous and configurable module loading is supported
- There are two types of exports,
named
exports anddefault
exports - named exports can be used to export multiple things using the keyword
export
- default exports used to export a default single value
- To import functions, objects and primitives that have been exported from an external module use the
import
statement - you may
import
an entire modules contents, a single member or multiple members in a module
A convenient way to specify named
exports as below:
// lib.js
// -------
export const quux = Math.sqrt( 2 ); // => exports a constant
export function multiply( x, y ) {
return x * y;
} // => exports a function
export function addContact( id, callback ) {
callback();
} // => exports a function
export function refreshContact() {
alert( `Hello ES6 Modules` );
} // => exports a function
And of course
import
them into another file:
// main.js
// -------
import quux from 'lib';
import { multiply, addContact, refreshContact } from 'lib';
console.log( quux ); // => 1.4142135623730951
console.log( multiply(10, 10); // => 1000
console.log(addContact(1, refreshContact)); // => alerts `Hello ES6 Modules`
// app.js
// ------
// import the whole module
import * as lib from 'lib'
console.log( lib.quux ); // => 1.4142135623730951
console.log( lib.refreshContact() ); // => alerts `Hello ES6 Modules`
Another technique in a module, we could use the following:
const quatro = 10 * 10 * 10 * 10;
export { quatro };
default
exports is simple one module that you want to export as the default ( like CommonJS ) and it turns out you can usedefault
exports andnamed
exports both in your module. There is alot more onmodules
inES6
includingscript
tags, usingpromises
andmodule loading
which is an API that allows you to programmatically work with modules and configure module loading.
Getters and setters allow you to use standard property access notation for reads and writes. In ES6
there is a much cleaner way than using Object.defineProperty
to do achieve this.
Getting
andSetting
helps you organize functionality associated with direct access
class Employee {
constructor(name) {
this._name = name;
}
get name() {
return `Employees name is: ` + this._name.toUpperCase();
}
set name(newName){
if(newName){
this._name = newName;
}
}
}
let developer = new Employee("Sophia");
console.log(developer.name); // => Sophia
developer.name = "Dania";
console.log(developer.name); // => Dania
Classes are a welcome addition in ES6
however do not introduce a new OO model and rather are just syntactical sugar
over JavaScript's existing prototype-based-inheritance model.
- To declare a class simply use the
class
keyword - There are two ways to define a class by using either
class declarations
orclass expressions
class declarations
are not hoistedclass expressions
can be named or unnamed- The
constructor
creates and initializes an object created with aclass
and theirbodies
are executed in strict mode - The
extends
keyword is used to create aclass
that is a child of another class - The
static
keyword is used to define a static method of aclass
class Atom {
constructor( name, mass, neutrons ) {
this._name = name;
this._mass = mass;
this._neutrons = neutrons;
}
// using get
get name() {
return this._name;
}
get mass() {
return this._mass;
}
get neutrons() {
return this._neutrons;
}
toString() {
return `${this.name}'s mass index is ${this.mass}, and is composed of ${this.neutrons} neutrons` ;
}
}
const particle = new Atom('Mote', 100000, 1000);
console.log(particle.mass);
console.log(particle.neutrons);
console.log(particle.toString());
There can only be one special method with the nameconstructor
however you can have many prototype
methods in your class
class Atom {
constructor( mass, neutrons) {
this.mass = mass;
this.neutrons = neutrons;
}
// prototype method
get printMass() {
return this.calcMass();
}
calcMass() {
return `Atom's mass is ${this.mass}`;
}
// prototype method
get printSize() {
return this.calcSize();
}
calcSize() {
return `Atom's size is ${this.mass * this.neutrons}`;
}
}
const square = new Atom(1000, 10);
console.log(square.printMass);
console.log(square.printSize);
We now have a public interface to use symbols in ES6
. A symbol is a unique and immutable primitive data type
.
- Their main purpose is
interoperability
and to avoid name clashes between properties - you can use symbols as identifiers when adding props to an object - Don't use the
new
operator when creating a symbol, just invoke the function - Symbols are unique, you cannot alter them ( which is the whole point )
- Interestingly symbols used as
keys
to an object will not appear as part of the object when using afor in
loop
Syntax for creating a symbol, Symbol
or Symbol( description )`:
let sym1 = new Symbol(); // => throws an error
let sym2 = Symbol( "symbol description" ); // => you can add a optional description
let sym3 = Symbol( "symbol description" ); // => both `sym1` and `sym2` are unique
Using symbols as keys into an object, when you iterate with
for in
your symbol identifier will be hidden:
const phoneNo = Symbol();
const employee = {
firstName: "Moe",
lastName: "Khan",
[ phoneNo ]: 555-555-5555
}
for (const key in employee) {
if (employee.hasOwnProperty(key)) {
alert(key + " -> " + employee[key]);
} // => key -> firstName: Moe, key -> lastName: Khan
}
Symbols can be useful for hiding information in an object, and there is a new object method named getOwnPropertySymbols
that will help you reflect into symbol props:
const phoneNo = Symbol();
let symbol0 = Object.getOwnPropertySymbols(employee);
const employee = {
firstName: "Moe",
lastName: "Khan",
[ phoneNo ]: 5555555555
}
for (const key in employee) {
if (employee.hasOwnProperty(key)) {
alert(key + " -> " + employee[key]);
}
if (symbol0) {
alert("Hidden symbol " + " -> " + employee[ phoneNo ]);
}
}
Generators are an exciting new feature and can be used as iterators
as well as drastically help us write asynchronous code
- When you
invoke
a function it runs till completion before any other code can run because JS is always single threaded - Generator functions allow you to pause a functions execution in a
synchronous
manner and return the value of an expression - Inside the generator function body use the new
yield
keyword to pause the function intrinsically - Use the
*
symbol to denote a generator function
Take a rudimentary example:
function *myGenerator() {
yield '1: First stab at generators';
yield 2;
yield '3: Use next() to start iterating';
yield 4
yield "5: Normal functions 'run to completion'";
}
let step = myGenerator();
console.log( step.next() ); // => { value: "1: First stab at generators", done: false }
console.log( step.next() ); // => { value: 2, done: false }
console.log( step.next() ); // => { value: "3: Use next() to start iterating", done: false }
console.log( step.next() ); // => { value: 4, done: false }
console.log( step.next() ); // => { value: "5: Normal functions 'run to completion'", done: false }
console.log( step.next() ); // => { value: undefined, done: true }
Furthering on our example and passing values into next('some value'); This might be a little confusing at first:
function *personFullName() {
var fName = yield 'first name';
var lName = yield 'second name';
console.log(fName + lName);
}
var myGenerator = personFullName();
myGenerator.next(); //{ value: 'first name', done: 'false' }
myGenerator.next('Maya '); //{ value: 'second name', done: 'false' }
myGenerator.next('Bethea') // { value:'undefined', done: 'true' }
// => Maya Bethea
Arrow functions are one of the more popular features of ES6
saving developer time and simplifying function scope.
- The arrow
(=>)
orfat arrow
provides a shorthand for the function keyword with lexicalthis
binding - Arrow functions change the way
this
binds in functions - They work much like
lambdas
in languages likeC#
orPython
lambda
expressions are are often passed as arguments to higher order functions
- we avoid the
function
keyword,return
keyword andcurly brackets
when using arrow functions - Arrow functions allow developers to remove boilerplate, however you shouldn't remain ignorant of how 'lexical
scoping
this` works - Be careful when using
arrow functions
as they aren't applicable everywhere, use cases will depend from situation to situation
For the sake of simplicity, we could do the following:
const msg = () => alert("Hello Arrow Function");
console.log(msg());
let square = ( a, b ) => a * b;
console.log(square(5, 5)); // 25
let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let z = x.map(x => x);
console.log(z); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Every new function defines it's own this
value, yet arrow
functions close over the this
value
// in ES5
function Tree() {
that = this;
that.age = 0;
setInterval(function growOld() {
that.age++;
}, 1000);
}
var t = new Tree();
// in ES6
function Tree() {
this.age = 0;
setInterval(() => {
this.age++;
5000);
}
}
var t = new Tree();
With the addition of new data structures that are new to ES6
, Map
has been sorely awaited by developers to map values to values - Objects
have been used as maps
historically
- The
Map
object is a simple key/value map and is deemed aniterable
for...of
loop returns an array of [key, value] for each iteration- While similar, there are certain distinctions between Objects and Maps and therefore are certain use-cases
- Most importantly
Map
instances are only useful for collections andObjects
used as records with fields and methods
At a fundamental level when working with maps you could do the following:
let map = new Map();
map.set( 'min', Number.MIN_SAFE_INTEGER );
map.set( 'max', Number.MAX_SAFE_INTEGER );
map.has('baz'); // => false
map.size; 2
console.log( map ); // => Map { "min" => -9007199254740991, "max" => 9007199254740991 }
map.delete( 'max' );
console.log( map ); // => Map { "min" => -9007199254740991 }
map.clear()
map.size; // => 0
Another way to set a map
via iterable `key-value "pairs". Notice that you can any value as the key:
let hash = new Map([
[ 'age', 1 ],
[ false, 'true' ],
[ function () {}, 'function' ],
[ {}, 'object' ],
[ 5, 'five' ],
[ undefined, 'undefined'],
[ null, 'null'],
[ Symbol(), 'Symbol']
]);
for ( let key of hash.keys()) {
console.log( typeof key );
} // => string, boolean, function, object, number, undefined, object, symbol
Note: The
for...of
statement creates a loop iterating only overiterable objects' (Array, Map, Set, String, etc). You can also use the built in
forEach` to iterate over map collection
Promise objects help us with asynchronous programming, espercially deferring async operations. Think of functions that return their results asychronously ( heard of callback hell? ).
- ES6 has adopted
[PromiseA+](https://promisesaplus.com/)
spec, but there are other libraries out there as such as RSVP, Bluebird, Q - This allows you to create handlers to asynchronous actions's eventual success or failure in a synchronous way
- So, promises have a sense of
state
which is either fullfilled, pending, or rejected
An example of callback hell, also known as the pyramid of doom
asyncFn1(function(err, result) {
asyncFn2(function(err, result) {
asyncFn3(function(err, result) {
asyncFn4(function(err, result) {
asyncFn5(function(err, result) {
// Do something
});
});
});
});
});
Promises
help flatten our structures, a most welcome struct:
asyncFn1(value1)
.then(asyncFn2)
.then(asyncFn3)
.then(asyncFn4)
.then(asycnFn5, value5 => {
// Do something with value5
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
An example of what a native promise
looks like:
var p = new Promise(function(resolve, reject) {
if (/* condition */) {
resolve(/* value */); // fulfilled successfully
}
else {
reject(/* reason */); // error, rejected
}
});
Proposals for the ES2017, ES7 specification can be found here: https://github.com/tc39/ecma262 - definitely check the spec when in doubt.
Some of the features will include:
Array.prototype.includes
- myArr.includes(value) // nice!
Async
andGenerator
functions- Exponential Operators
Please look at my es7 repository for more information on specific features
Contributions in the form of code samples and syntax are welcome - if you spot errors in example code or feel you have a better use case for a given feature issue a pull request. Typos and formatting requests will also be considered.