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,ActionScriptandJScriptare implementations of it - JavaScript popularized two paradigms on the web,
functional programmingandprototypal inheritance(objects without classes) JavaScriptfirst 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
ES5andES6. - 'ES6' is the first ECMAScript Harmony specification and is also known as
ES Harmony,ES2015,ES.nextbut commonly referred to asES6. - The specification was finalized in
June 2015as it reachedfeature completestatus- Subsequent versions will be released on a 12 month cadence, and will be dubbed
ES7,ES2016respectively.
- 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
ES2015andES2016 - The most popular JavaScript transpilers are
Babeland was previously known as6to5andtraceur - 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 compilesES6toES5and you don't have to install anything. - Another web based tool is
Scratch JSand comes in the form of a chrome extension. This interactive playground let's you transpile your code in the all familiardev toolsenvironment.
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' andbabel node`
Using an IDE:
- You can easily use
Babelto 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
ES6features 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 definedIn ES5 you would use the
functionas 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'
letis block scoped and is hoisted to the top of it's block, unlikevarwhich is hoisted to the top of it's function or scriptconstis 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`
letdoes not create a property on the global objectconstcreates immutable variables, whilstletcreate mutable ones- a duplicate declaration of
letwill throw a Reference Error within a block or function - this is also known as
TDZor temporal dead zone - Basic rules to follow:
- don't use
var, or leave it as a signal in untouched legacy code. - use
letwhen you want to rebind. - prefer
constfor variables that never change. - don't blindly refactor legacy code when replacing
varwithletandconst.
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
spreadandrestoperators work with destructuring- You can destructure a
for-ofloop
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: 6We 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.
ES6introducesstring interpolation,string formatting,multiline stringsandembedded expressionswith 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 literalshave been added includedoctalandbinaryliterals - Methods that have been added to
Numberapart from the usual four suspects are,Number.EPSILON,Number.isInteger,Number.isNaN,Number.isFinite,number.isSafeInteger Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGERare 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); // => 100The 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); // => 3note: the arguments object is not a real array - rest params are instances of
Arrayso 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, 3At 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
ES6is to keep bothCommonJSandAMDuser happy with a single format and borrows the best from both worlds ES6modules 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,
namedexports anddefaultexports - 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
importstatement - you may
importan 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 functionAnd of course
importthem 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 };
defaultexports is simple one module that you want to export as the default ( like CommonJS ) and it turns out you can usedefaultexports andnamedexports both in your module. There is alot more onmodulesinES6includingscripttags, usingpromisesandmodule loadingwhich 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.
GettingandSettinghelps 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); // => DaniaClasses 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
classkeyword - There are two ways to define a class by using either
class declarationsorclass expressions class declarationsare not hoistedclass expressionscan be named or unnamed- The
constructorcreates and initializes an object created with aclassand theirbodiesare executed in strict mode - The
extendskeyword is used to create aclassthat is a child of another class - The
statickeyword 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
interoperabilityand to avoid name clashes between properties - you can use symbols as identifiers when adding props to an object - Don't use the
newoperator when creating a symbol, just invoke the function - Symbols are unique, you cannot alter them ( which is the whole point )
- Interestingly symbols used as
keysto an object will not appear as part of the object when using afor inloop
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 uniqueUsing symbols as keys into an object, when you iterate with
for inyour 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
invokea 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
synchronousmanner and return the value of an expression - Inside the generator function body use the new
yieldkeyword 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 BetheaArrow functions are one of the more popular features of ES6 saving developer time and simplifying function scope.
- The arrow
(=>)orfat arrowprovides a shorthand for the function keyword with lexicalthisbinding - Arrow functions change the way
thisbinds in functions - They work much like
lambdasin languages likeC#orPythonlambdaexpressions are are often passed as arguments to higher order functions
- we avoid the
functionkeyword,returnkeyword andcurly bracketswhen using arrow functions - Arrow functions allow developers to remove boilerplate, however you shouldn't remain ignorant of how 'lexical
scopingthis` works - Be careful when using
arrow functionsas 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)); // 25let 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
Mapobject is a simple key/value map and is deemed aniterable for...ofloop 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
Mapinstances are only useful for collections andObjectsused 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; // => 0Another 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, symbolNote: The
for...ofstatement creates a loop iterating only overiterable objects' (Array, Map, Set, String, etc). You can also use the built inforEach` 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
statewhich 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!
AsyncandGeneratorfunctions- 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.