&e Tech

Javascript Workshop

To ES6 and beyond!

&e Tech

Let's clear up som "ES" confusion!

  • ES stands for ECMAScript
  • ECMAScript is the real name for JavaScript
  • Sometimes you may see ES20XX - this is the old way of defining JS versions eg ES2015
  • Nowadays we use a version number a la ES6
  • Often ES5 is used synonymously with "anything before ES6"
&e Tech

String interpolation

var username = 'Bill';

// Old style
var helloString = 'Hello ' + username + '!';

// new style
var helloString = `Hello ${username}!`;

// result
helloString === 'Hello Bill!'
&e Tech

Optional chaining

// old style (shudder!)
var email = (user && user.contact && user.contact.email) ? user.contact.email : null;

// new style
var email = user?.contact?.email
&e Tech

Object properties shorthand

var id = 1;
var username = 'Bill';

// old style
var user = { id: id, username: username };

// new style
var user = { id, username };

// result
user === { id: 1, username: 'Bill'};
&e Tech

Spread operator

&e Tech

In arrays

var otherNumbers = [3, 4];

// old style
var numbers = [1, 2].concat(otherNumbers);

// new style
var numbers = [1, 2, ...otherNumbers];

// result
numbers === [1, 2, 3, 4];
&e Tech

In objects

var userAttributes = { id: 1, username: 'andy' };
var updatedUserAttributes = { username: 'Bill', firstName: 'Stephen' };
// old style
var user = {};
Object.keys(userAttributes).forEach(function (key) {
    user[key] = userAttributes[key];
});
Object.keys(updatedUserAttributes).forEach(function (key) {
    user[key] = updatedUserAttributes[key];
});
// new style
var user = { ...userAttributes, ...updatedUserAttributes };
// or
var user = Object.assign(
  {},
  userAttributes,
  updatedUserAttributes
);
// result
user === { id: 1, username: 'Bill', fistName: 'Stephen' };
&e Tech

In functions

function mergeUsers(userA, userB) {
  return { ...userA, ...userB };
}

var usersToMerge = [
  { firstName: 'Bill', lastName: 'Jobs' },
  { firstName: 'William' }
];
// old style
mergeUsers(usersToMerge[0], usersToMerge[1]);
// or
mergeUsers.apply(this, usersToMerge);
// new style
mergeUsers(...usersToMerge);
&e Tech

Rest parameter

var userToMergeTo = { id: 1, username: 'Bill' };
var otherUser1 = { id: 2, firstName: 'William' };
var otherUser2 = { id: 4, lastName: 'Jobs' };

var result = mergeUsers(userToMergeTo, otherUser1, otherUser2);

// result should preserve the id of the userToMergeTo
result === { id: 1, username: 'Bill', firstName: 'William', lastName: 'Jobs' }
// Old style
function mergeUsers(userToMergeTo) {
  var otherUsers = Array.prototype.slice.call(arguments, 1);

  otherUsers.forEach(function (user) {
    Object.keys(user).forEach(function (key) {
      if (key !== 'id') userToMergeTo[key] = user[key];
    });
  });

  return userToMergeTo;
}
// new style (similar to python's *kwargs or ruby's splat (*))
function mergeUsers(userToMergeTo, ...otherUsers) {
  otherUsers.forEach(function (user) {
    Object.keys(user).forEach(function (key) {
      if (key !== 'id') userToMergeTo[key] = user[key];
    });
  });

  return userToMergeTo;
}
&e Tech

Destructuring Assignments

&e Tech

Array matching

var numbers = [1, 2];

// Old style
var one = numbers[0];
var two = numbers[1];

// New style array matching
var [one, two] = numbers;
&e Tech

Array matching - default values

var numbers = [1];

// Old style
var one = numbers[0];
var two = numbers[1] === undefined ? 2 : numbers[1];;

// New style array matching
var [one, two = 2] = numbers;
&e Tech

Object matching

var user = { id: 1, username: 'andy' };

// Old style
var id = user.id;
var username = user.username;

// New style
var { id, username } = user;
&e Tech

Deep object matching

var user = { id: 1, username: 'andy', contact: { email: 'andy@example.com' } };

// Old style
var email = user.contact.email;

// New style
var { contact: { email } } = user;
&e Tech

Object matching - default values

var user = { id: 1, username: 'andy' };

// Old style
var id = user.id;
var username = user.username === undefined ? "anonymous" : user.username;

// New style
var { id, username = "anonymous" } = user;
&e Tech

Parameter destructuring

&e Tech

With array parameters

var usersToMerge = [
  { firstName: 'Bill', lastName: 'Jobs' },
  { firstName: 'William' }
];
mergeUsers(usersToMerge);
// Old style
function mergeUsers(usersArray) {
  var userA = usersArray[0];
  var userB = usersArray[1];
  return {
    ...userA,
    ...userB
  };
}
// New style
function mergeUsers([userA, userB]) {
  return {
    ...userA,
    ...userB
  };
}
&e Tech

With object parameters

// Old style
function sayHello(user) {
  var username = user.username;
  return `Hello ${username}`;
}

// New style
function sayHello({ username }) {
  return `Hello ${username}`;
}
&e Tech

Import and export destructuring

// my-module.js
const myFunction = function () {};
const myConstant = Math.PI;
const MyModule = { myFunction, myConstant };

// exporting
export default MyModule;
// or
export myFunction;
export myConstant;

// importing
import 'my-module';
import MyModule from 'my-module';
import MyModule as TheirModule from 'my-module';
import MyModule, { myFunction, myConstant as theirConstant } from 'my-module';
&e Tech

Arrow functions

// Old style function
function doSomething (someValue) {
  return someValue + 1;
}
// or
var doSomething = function (someValue) {
  return someValue + 1;
}

// new arrow style function
var doSomething = someValue => {
  return someValue + 1;
}
// or with an implicit return
var doSomething = someValue => someValue + 1;
&e Tech

Usage as inline callback

// Old style function
var usernames = users.map(function (user) {
  return user.username;
});

// new arrow style function
var usernames = users.map(user => user.username);
&e Tech

Lexical this - What is this?

  • Well... it depends!
  • by default it is likely window or global
  • using strict mode there is no default binding, it is undefined
  • in an object it is the object itself
  • in DOM event listeners it will be the event.target node
  • usually though, this is related to whatever called the function asking for this
this.numbers = [...Array(50).keys()];
this.multiplesOfFive = [];

this.numbers.forEach(function (number) {
  if (isMultiplesOfFive(number)) {
    // "this" here could be undefined or
    // it could be something else depending
    // on how this function is called
    this.multiplesOfFive.push(number);
  }
});
&e Tech

Lexical this - the old options

// option 1
var that = this;
this.numbers.forEach(function (number) {
  if (isMultipleOfFive(number)) that.multiplesOfFive.push(number);
})

// option 2
this.numbers.forEach(function (number) {
  if (isMultipleOfFive(number)) this.fives.push(number);
}, this)

// option 3
this.numbers.forEach(function (number) {
  if (isMultipleOfFive(number)) this.fives.push(number);
}.bind(this))
&e Tech

Lexical this - arrow style

  • In arrow functions this is always the object which defined the function
  • you cannot use them for constructors (functions which use the new keyword)
this.numbers.forEach(number => {
  if (isMultipleOfFive(number)) this.multiplesOfFive.push(number);
})
&e Tech

Arrow functions - but when and why?

  • We can use arrow functions almost interchangeably for normal functions
  • We cannot use them in constructors
  • Knowing when and why is often a case of style unless you explicitly need special lexical this scoping or not
&e Tech

Promises

// Old style function
function doSomething (someId, onSuccess, onFailure, onFinally) {
  try {
    var data = getSomeData(someId);
    onSuccess(data);
  } catch (error) {
    onFailure(error);
  } finally {
    onFinally();
  }
}

doSomething(1, console.log, console.error, function () {
  // this always happens!
});
// new Promise style
function doSomething (someId) {
  return new Promise((resolve, reject) => {
    try {
      var data = getSomeData(someId);
      resolve(data);
    } catch (error) {
      reject(error);
    }
  })
}

doSomething(1)
  .then(data => {
    var modifiedData = doSomethingWithData(data);
    return data;
  })
  .then(console.log)
  .catch(console.error)
  .finally(() => {
    // This always happens!
  })
&e Tech

Throwing errors

function doSomething () {
  return new Promise((resolve, reject) => {
    throw new Error("Ro roh!");
  })
}

doSomething().catch(error => {
  console.log(error);
  sendErrorToSomeErrorTrackingService(error);
})
&e Tech

Promise.all()

function runAll(functions, onSuccess, onError) {
  var results = [];
  var overallError;

  functions.forEach(function (func, index) {
    function handleFuncSuccess(data) {
      results[index] = data;
    }

    function handleFuncError(error) {
      overallError = error;
      break;
    }

    func(handleFuncSuccess, handleFuncError);
  });

  if (overallError) {
    onError(overallError);
  } else {
    onSuccess(results);
  }
}

function func1(onSuccess) { onSuccess('great success!') }
function func2(onSuccess) { onSuccess('yes, very nice') }
runAll([func1, func2], function (data) {
  data === [
    'great success!',
    'yes, very nice'
  ]
});

function func1(onSuccess) { onSuccess('great success!') }
function func2(onSuccess, onFailure) { onFailure('fail') }
runAll([func1, func2], null, function (error) {
  error === 'fail';
});
Promise.all([
  Promise.resolve('great success!'),
  Promise.resolve('yes, very nice')
]).then(data => {
  // data === [
  //   'great success!',
  //   'yes, very nice'
  // ]
})

Promise.all([
  Promise.resolve('great success!'),
  Promise.reject('fail')
]).catch(error => {
  // error === 'fail'
})
&e Tech

Promise.allResolved()

Promise.allResolved([
  Promise.resolve('great success'),
  Promise.reject('failure')
]).then(data => {
  // data === [
  //   { status: 'fulfilled', value: 'great success' },
  //   { status: 'rejected', reason: 'failure' }
  // ]
})
// No catch needed here as the failed result is included in the result data
&e Tech

async / await

function fetchDataFromApi () {
  return new Promise((resolve, reject) => {
    try {
      var data = getData();
      resolve(data);
    } catch (error) {
      reject(error);
    }
  });
}

// Trigger the data request then carry on doing other stuff
fetchDataFromApi().then(data => {
  // do something with the data
  // when it eventually completes
})

// continue working on other stuff
async function fetchDataFromApi () {
  var promise = new Promise((resolve, reject) => {
    try {
      var data = getData();
      resolve(data)
    } catch (error) {
      reject(error)
    }
  });

  return await promise;
}

// function will wait for promis to be resolved
// and assign the result before continuing
let data = fetchDataFromApi();

// continue working on other stuff
&e Tech

Classes

// old style
function Shape (x, y) {
    this.x = x;
    this.y = y;
}

Shape.prototype.move = function (x, y) {
    this.x = x;
    this.y = y;
};

var shape = new Shape(x, y);
shape.move(newX, newY);
// new style
class Shape {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  move(x, y) {
    this.x = x;
    this.y = y;
  }
}

var shape = new Shape(x, y);
shape.move(newX, newY);
&e Tech

Inheritance

// old style
function Shape (sides) {
  this.sides = sides;
}

function Triangle () {
  Shape.call(this, 3);
}

Triangle.prototype = Object.create(
  Shape.prototype
);
// new style
class Shape {
  constructor(sides) {
    this.sides = sides;
  }
}

class Triangle extends Shape {
  constructor() {
    super(3);
  }
}
&e Tech

Modifying inhereted functionality

// old style
function Shape (sides) {
  this.sides = sides
}

Shape.prototype.toString = function () {
  return "I have " + this.sides + " sides";
};

function Triangle () {
  Shape.call(this, 3);
}
Triangle.prototype = Object.create(Shape.prototype);

Triangle.prototype.toString = function () {
  return "As a Triangle " + Shape.prototype.toString.call(this);
}

var shape = new Shape(4);
shape.toString() === "I have 4 sides"

var triangle = new Triangle();
triangle.toString() === "As a Triangle I have 3 sides"
// new style
class Shape {
  constructor (sides) {
    this.sides = sides;
  }

  toString () {
    return "I have " + this.sides + " sides";
  }
}

class Triangle extends Shape {
  constructor () {
    super(3)
  }

  toString () {
    return "As a Triangle " + super.toString();
  }
}

var shape = new Shape(4);
shape.toString() === "I have 4 sides"

var triangle = new Triangle();
triangle.toString() === "As a Triangle I have 3 sides"
&e Tech

Static members

// old style
function Shape (sides) {
  this.sides = sides;
}

Shape.defaultShape = function () {
  return new Shape(3);
}

var shape = Shape.defaultShape();
shape.sides === 3;
typeof shape === "object";
shape.__proto__.constructor.name === "Shape";
// new style
class Shape {
  constructor (sides) {
    this.sides = sides;
  }

  static defaultShape () {
    return new Shape(0);
  }
}

var shape = Shape.defaultShape();
shape.sides === 3;
typeof shape === "object";
shape.__proto__.constructor.name === "Shape";
&e Tech

Getters and setters

// old style
function Shape (sides) {
  this._sides = sides;
}

Shape.prototype = {
    set sides (sides) {
      this._sides = sides;
    },

    get sides () {
      return this._sides;
    }
};

var shape = new Shape(0);
shape.sides = 2
shape.sides === 2
// new style
class Shape {
  constructor(sides) {
    this._sides = sides;
  }

  set sides (sides) {
    this._sides = sides;
  }

  get sides () {
    return this._sides;
  }
}

var shape = new Shape(0);
shape.sides = 2
shape.sides === 2