ES6 essentials

ES2015

ES Harmony

by Kiril Knysh

PL releases

ES2015 browsers support

March 2016

Desktop

91%
85%
53%
79%

Mobile

29%
53%

Trans (comp) pilers

March 2016

Features overview

let / const

block-scoped


for (let i = 0; i < count; i++) {
  ...
}
//i is not defined here
					

if (value <= maxValue) {
  let newValue = value * 0.1; 
}
//newValue is not defined here
					

let / const

constrained by TDZ semantics


function execute() {
  console.log(value);
  var value = 10;
  console.log(value);
  ...
}
					

function execute() {
  console.log(value); //Uncaught ReferenceError: value is not defined
  let value = 10;
  console.log(value);
  ...
}
					

function execute() {
  let value = 10;
  console.log(value); //10
  ...
}
					

const

single-assignment


function execute() {
  const value = 10;
  ...
  value = 15; //Uncaught TypeError: Assignment to constant variable
}
					

function execute() {
  const value = { tax: 10, mult: 0.4 };
  ...
  value.tax = 15; //OK!
}
					

Template strings(literals)

declared with `backticks`


`I am template`
					

can be multiline


`I am
template`
					

allow interpolation


const value = 10;
function getMult() { return 0.4; }

`Value is ${value};
Mult is ${getMult()};
2 + 2 = ${2 + 2}`
					

Enhanced object literals

ES5
function createChannel(id) {
  var channel = {
    id: id,
    toString: function() { return this.id; }
  };
  
  channel[Constants.KEY_1] = "Value 1";
  channel[getKey()] = "Value 2";
  
  return channel;
}
				
ES2015
function createChannel(id) {
  return {
    id,
    toString() { return this.id + super.toString(); }, //id + [object Object]
    [Constants.KEY_1]: "Value 1",
    [getKey()]: "Value 2"
  };
}
				

Destructuring

ES5
var id = channel.id;
var num = channel.num;
var url = channel.pictures[0] || 'default.png';
					
ES2015
const { id, num, pictures: [url='default.png'] } = channel;
					

works with arrays


const [zero, , two] = [0, 1, 2, 3, 4];
console.log(zero); //0
console.log(two); //2
						

swap values


[left, right] = [right, left];
						

Destructuring

in function params


function printChannel({ id, num }) {
  console.log(`ID=${id}, NUM=${num}`);
}

printChannel(channel);
					

function printChannel({ id='', num=0 } = {}) {
  console.log(`ID=${id}, NUM=${num}`); //ID=, NUM=0
}

printChannel();
					

spread + rest

spread


function getDistance(x1, y1, x2, y2) {
  return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}

const point1 = [10, 5];
const point2 = [12, 9];
getDistance(...point1, ...point2);
					

rest


function execute(fun, context, ...params) {
  return fun.apply(context, params);
}

execute(Math.max, Math, 10, 20, 5); //20
					

Fat-arrow functions


const fetchChannels = (startIndex, count) => {
  return CHANNELS.splice(startIndex, count);
}
					

shorthands


//no parenthesis for params, implicit return
channels.map(channel => channel.id);

//to return an object implicitly, wrap it in parenthesis
channels.map(channel => ({
  channelId: channel.id,
  poster: channel.pictures[0]
}));

//2 and more params
channels.map((channel, index) => {
  const poster = channel.pictures[0] + `?index=${index}`;

  return { channelId: channel.id, poster };
});
						

Fat-arrow functions

are bound to their lexical scope


const channel = {
  pictures: ['https://...', 'https://...'],
  size: { w: '300', h: '200' },
  printPicturesUrl() { 
    this.pictures.forEach((url) => {
      console.log(`${url}?w=${this.size.w}&h=${this.size.h}`);
    });
  }
}
						

don't have names, but


function mapChannels(channels) {
  return channels.reduce((result, channel) => {
    channel.pictures.forEach((picture) => {
      //...
    });
  }, []);
}
							

Classes


class Animal {
  constructor(name, color) {
    this.name = name;
    this.color = color;
  }

  //static function
  static className() {
    return 'Animal';
  }

  //getter property
  get stringified() {
    return `${this.name} - ${this.color}`;
  }
}
					

Classes

inheritance


class Dog extends Animal {
  constructor(name, color, birthDay) {
    super(name, color);

    this.birthDay = birthDay;
  }

  static className() {
    return 'Dog';
  }

  get stringified() {
    return `${super.stringified} : ${this.birthDay}`;
  }
}
					

Modules

files that export an API

with strict mode by default

Modules

exports

default export
export default function(channel) {
  return channel && channel.logo || null;
}
					
named export
export const LOGO_KEY = 'channel-logo-key-1';
					
multiple exports
function getName(channel) { return channel && channel.name || ''; }
const LOGO_KEY = 'channel-logo-key-1';

export { getName, LOGO_KEY };

export default function(channel) {
  return channel && channel.logo || null;
}
					

Modules

imports

load module
import 'channels-helper';
					
import default as
export default function(channel) {
  return channel && channel.logo || null;
}

import getChannelLogo from 'channels-helper';
					
import named exports
export const LOGO_KEY = 'channel-logo-key-1';

import { LOGO_KEY } from 'channels-helper';
import { LOGO_KEY as channelLogoKey } from 'channels-helper';
					

Modules

imports

mixed import
function getName(channel) { return channel && channel.name || ''; }
const LOGO_KEY = 'channel-logo-key-1';
export { getName, LOGO_KEY };
export default function(channel) {
  return channel && channel.logo || null;
}

import getChannelLogo, { getName as getChannelName } from 'channels-helper';
					
import namespace
function getName(channel) { return channel && channel.name || ''; }
const LOGO_KEY = 'channel-logo-key-1';
export { getName, LOGO_KEY };

import * as ChannelsHelper from 'channels-helper';
					

Promises


const p = new Promise((resolve, reject) => {
  //do some async work
  //...
  if (result) {
    resolve(result);
  } else {
    reject(error);
  }
});
					

follow Promises/A+ spec


p.then(
  (result) => {
    //handle success
  },
  (error) => {
    //process error
  }
);
						

Promises

support chaining


fetchChannels()
  .then((channels) => {
    return fetchEvents(channels);
  }).then((events) => {
    return processEvents(events);
  }).catch((error) => {
    log.error(error); //catches Promise reject + exceptions
  });
						

static methods


//create resolved Promise
Promise.resolve(value);
//create rejected Promise
Promise.reject(error);
//wait for all Promises to be resolved or at least one rejected
Promise.all([fetchChannels(), fetchProfile()]);
//wait for the 1st resolved/rejected Promise
Promise.race([fetchChannels1(), fetchChannels2()]);
						

Iterators

objects that know how to access items from a collection

implement the @@iterator method (property with a Symbol.iterator key)


const channels = {};
channels[Symbol.iterator] = function* () {
  yield 'BBC';
  yield 'MTV';
  yield 'Fashion TV';
}

[...channels]; //['BBC', 'MTV', 'Fashion TV']
for (let channel of channels) { console.log(channel); }
//BBC
//MTV
//Fashion TV
					

Generators

special type of functions that work as a factory for iterators


function* getChannelsIterator() {
  yield* ['BBC', 'MTV', 'Fashion TV'];
}

const channels = getChannelsIterator();

channels.next(); //{value: "BBC", done: false}
channels.next(); //{value: "MTV", done: false}
channels.next(); //{value: "Fashion TV", done: false}
channels.next(); //{value: undefined, done: true}
					

Generators

internal state


function* getUniqId() {
  let current = 0;

  while (true) {
    let reset = yield current;
    current = reset ? 0 : ++current;
  }
}

const generator = getUniqId();
generator.next(); //{value: 0, done: false}
generator.next(); //{value: 1, done: false}
generator.next(); //{value: 2, done: false}
generator.next(true); //{value: 0, done: false}
generator.next(); //{value: 1, done: false}
					

Map


const requestParams = { w: 300, h: 200 };
const response = fetch(requestParams);

const map = new Map([ ['key-1', 'value-1'], [requestParams, response] ]);

map.set('key-2', 'value-2');
map.get(requestParams) === response;
map.delete('key-1');
map.has('key-1') === false;
						

iterate


map.forEach((value, key) => { ... });
for (let [key, value] of map) { ... }
Array.from(map).forEach(([key, value], index) => { ... });
_.forEachRight([...map], ([key, value], index) => { ... });
							

WeakMap


const cache = new WeakMap();

cache.set(channel, getMetaData(channel));
cache.get(channel);
					
    similar to Map but:
  • weak references to keys
  • keys must be reference types
  • not iterable

Set


const set = new Set([1, 2, 3]);

set.size; //3
set.add(2);
set.size; //3
set.has(1) === true;
					

iterate


set.forEach((value) => { ... });
for (let value of set) { ... }
Array.from(set).forEach((value, index) => { ... });
_.forEachRight([...set], (value, index) => { ... });
						

WeakSet


const cache = new WeakSet();

cache.add(channel);
cache.has(channel) === true;
					
    similar to Set but:
  • weak references to values
  • values must be reference types
  • not iterable

Proxies

useful for interception, logging/profiling, data mocks, etc


const channel = { id: 'mtv', num: 12 };
const handler = {
  get: (receiver, name) => {
    if (name === 'num') {
      return `${receiver.id} - ${receiver.num}`;
    }

    return receiver[name];
  }
};

const prox = new Proxy(channel, handler);

prox.id === 'mtv';
prox.num === 'mtv - 12';
					

Proxies

freeze objects


const channel = { id: 'mtv', num: 12 };

function preventChange() { throw new Error('Object is frozen'); }
const handler = {
  set: preventChange,
  defineProperty: preventChange,
  deleteProperty: preventChange,
  preventExtensions: preventChange,
  setPrototypeOf: preventChange
};

const prox = new Proxy(channel, handler);

prox.id = 'bbc'; //Uncaught Error: Object is frozen
					

Proxies

    traps
  • getproxy.prop, proxy['prop']
  • setproxy.prop = value, proxy['prop'] = value
  • hasin operator
  • deletePropertydelete operator
  • applyfunction calls
  • constructnew operator
  • definePropertyObject.defineProperty, declarative alternatives
  • enumeratefor..in loops
  • ownKeysObject.keys and related methods
  • ...
©aptain America ©aptain America

Thank you!

Kiril Knysh