A Short Overview of ES612/06/2016

ES6 (ECMAScript6) is one of the latest versions of JavaScript that allows you to be a little more flexible with your code. It offers a series of array helpers and syntax sugar that makes coding a lot more elegant without relying on external libraries (like loadash, underscore, and CoffeeScript) to do the same thing. Now here's a quick review of ES6 starting with the Array Helpers.

ES6 Array Helpers

Array helper methods (lodash/underscore) are now native to ES6. These helper methods are used quite commonly in web development. They're normally used when working with collections of data. Using array helpers help us stay away from using for loops.

forEach

Here's a for loop that console logs each color inside an array:

var colors = [ 'red', 'blue', 'green' ];

for (var i = 0; i < colors.length; i++) {
    console.log(colors[i]);
}

We don't want to write for loops because they're not pure functions, meaning they mutate data. They're also require more effort (a lot of elements in the code) to understand when looking at someone else's code. Here is the same loop using the 'forEach' helper method:

colors.forEach(function(color) {
    console.log(color);
});

Here's another example of the forEach method:

// create an array of numbers
var numbers = [1,2,3,4,5];

// create a variable to hold the sum
var sum = 0;

function add(number) {
    sum += number;
}

// loop over the array, incrementing the sum variable
numbers.forEach(add);

// print the sum variable
sum; // 15

map

Here's a standard for loop that doubles the numbers inside an array:

var numbers = [1,2,3];
var doubledNumbers = [];

for (var i = 0; i < numbers.length; i++) {
  doubledNumbers.push(numbers[i] * 2);
}

doubledNumbers; // [2,4,6]

Here's the code refactored to use the map array function:

var doubled = numbers.map(function(number) {
    return number * 2;
});

doubled; // [2,4,6]

This is a much cleaner solution because it creates a new array without having to mutate an existing array.

Here's a more complex example using the map method:

var cars = [
  { model: 'Buick', price: 'cheap' },
  { model: 'Camaro', price: 'expensive' }
];

// find the prices for each car
var prices = cars.map(function(car) {
    return car.price;
});

prices; // ['cheap','expensive']

Where would we use map in practical programing? Imagine a collection of posts where you need to update a specific key inside the post object. You would use the map array helper to achieve that.

filter

Here's a for loop that filters objects with a specific key:

var products = [
  { name: 'cucumber', type: 'vegetable' },
  { name: 'banana', type: 'fruit' },
  { name: 'celery', type: 'vegetable' },
  { name: 'orange', type: 'fruit' }
];

var filteredProducts = [];

for (var i = 0; i < products.length; i++) {
    if (products[i].type === 'fruit') {
    filteredProducts.push(products[i]);
  }
}

filteredProducts;
// printed result:
// [{"name":"banana","type":"fruit"},
// {"name":"orange","type":"fruit"}]

Now, here's that code refactored to use the filter method:

products.filter(function(product) {
    return product.type === 'fruit'
});

This is a much more elegant way to write code. You're also not mutating an existing array. The filter method only returns the filtered products that you've specified inside a new array.

Here's a more complex example of the filter method:

var products = [
  { name: 'cucumber', type: 'vegetable', qty: 0, price: 1 },
  { name: 'banana', type: 'fruit', qty: 10, price: 15 },
  { name: 'celery', type: 'vegetable', qty: 30, price: 9 },
  { name: 'orange', type: 'fruit', qty: 3, price: 5 }
];

// write a filter where:
// the type is vegetable,
// the qty is greater than 0,
// and the price is less than 10

products.filter(function(product) {
    return product.type === 'vegetable'
      && product.qty > 0
      && product.price < 10
});

// this returns:
// [{"name":"celery","type":"vegetable","qty":30,"price":9}]

Where would you use filter in a practical example. Imagine a set of posts with child comments. You can filter the comments by the post associated with that comment by using the filter helper method. It would look something like this:

var post = { id: 4, title: 'New Post' };
var comments = [
  { postId: 4, content: 'awesome post' },
  { postId: 3, content: 'it was ok' },
  { postId: 4, content: 'nice post' }
];

function commentsForPost(post, comments) {
    return comments.filter(function(comment) {
    return comment.postId === post.id;
  });
}

commentsForPost(post, comments);

// this returns:
// [{"postId":4,"content":"awesome post"},
// {"postId":4,"content":"nice post"}]

find

Here's a for loop that searches through an array and finds a particular element inside that array:

var users = [
  { name: 'Jill' },
  { name: 'Alex' },
  { name: 'Bill' }
];

var user;

for (var i = 0; i < users.length; i++) {
    if (users[i].name === 'Alex') {
      user = users[i];
    break;
  }
}

user; // {"name":"Alex"}

Here's the code refactored to use the find method:

users.find(function(user) {
    return user.name === 'Alex';
});

This is a much cleaner way to do this. The find helper will iterate through the array until it finds the first element that you're looking for and break out of the loop.

Here's a more complex example of the find helper:

function Car(model) {
    this.model = model;
}

var cars = [
  new Car('Buick'),
  new Car('Camaro'),
  new Car('Ford')
];

cars.find(function(car) {
    return car.model === 'Ford';
});

// returns: {"model":"Ford"}

Another example for finding post for it's comment:

var posts = [
  { id: 1, title: 'New Post' },
  { id: 2, title: 'Old Post' }
];

var comment = { postId: 1, content: 'Great Post' };

function postForComment(posts, comment) {
    return posts.find(function(post) {
      return post.id === comment.postId;
  });
}

postForComment(posts, comment);
// returns: {"id":1,"title":"New Post"}

every and some

Here's a for loop example that checks whether a computer that can run a specific software program or not by checking the size of the computer's ram:

var computers = [
  { name: 'Apple', ram: 24 },
  { name: 'Compaq', ram: 4 },
  { name: 'Acer', ram: 32 }
];

var allComputersCanRunProgram = true;
var onlySomeComputersCanRunProgram = false;

for (var i = 0; i < computers.length; i++) {
    var computer = computers[i];

  if (computer.ram < 16) {
      allComputersCanRunProgram = false;
  } else {
      onlySomeComputersCanRunProgram = true;
  }
}

allComputersCanRunProgram; // false
onlySomeComputersCanRunProgram; // true

As you can probably tell this code is annoying to read. It will take some time to understand it. Here's a refactored version of this code using the every and some methods:

computers.every(function(computer) {
    return computer.ram > 16;
});

Since not every computer has more than 16 ram the function returns as false. Now let's check if some of the computers has more than 16 ram:

computers.some(function(computer) {
    return computer.ram > 16;
});

This function returns as true because there's two computers listed with more than 16 ram.

Here's another example of every and some helpers:

var names = [
  'Alexandria',
  'Matthew',
  'Joe'
];

names.every(function(name) {
    return name.length > 4;
});

// returns: false

names.some(function(name) {
    return name.length > 4;
});

// returns: true

Where would we use the every and some helpers? Let's look at a practical example where we're building a login form with validation:

function Field(value) {
    this.value = value;
}

Field.prototype.validate = function() {
    return this.value.length > 0;
}

var username = new Field('username');
var password = new Field('password');
var dob = new Field('10/10/2010');

var fields = [username, password, dob];

var formIsValid = fields.every(function(field) {
    return field.validate();
});

// returns true

if (formIsValid) {
    // submit form
} else {
    // show error
}

reduce

Reduce is a very flexible helper. Let's look at a for loop example first:

var numbers = [10,20,30];
var sum = 0;

for (var i = 0; i < numbers.length; i++) {
    sum += numbers[i];
}

Now here's the same code refactored to use the reduce method:

numbers.reduce(function(sum, number) {
    return sum + number;
}, 0);

// returns 60

The difference between reduce the the other helpers is that we can pass two arguments. In this example, the sum is the initial value of 0. The number argument is then added to the initial value which changes the first argument (sum) to whatever the number that was added to it. The end result is 60, the reduce method added our numbers together giving us the final sum of those numbers.

Now let's look at a more complex example to hammer in how the reduce helper works:

var primaryColors = [
  { color: 'red' },
  { color: 'yellow' },
  { color: 'blue' }
];

// we want to reduce the objects to strings inside an array
primaryColors.reduce(function(previous, primaryColor) {
    previous.push(primaryColor.color);

  return previous;
}, []);

// returns ['red','yellow','blue']

Let's look at a practical example of the reduce helper (it's not just used to sum numbers):

// given a string that contains some number of parantheses,
// are the expressions correctly balanced?

function balancedParens(string) {
    return !string.split("").reduce(function(previous, char) {
    // check if previous becomes a negative value
    if (previous < 0) { return previous; }
      // if open parens increase previous by 1
    if (char === "(") { return ++previous; }
    // if closed parens decrease previous by 1
    if (char === ")") { return --previous; }
    return previous;
  }, 0);
}

balancedParens("(((("); // false
balancedParens("()()()"); // true
balancedParens(")("); // false

That's it for covering array helpers.

ES6 const and let

  • const - a variable where we expect the value to never change.
  • let - a variable where we expect the value to change at some time.

Template Strings / Literals

It is now more convenient to use objects inside strings:

`The year is ${new Date().getFullYear()}`;

Fat Arrow Functions

const add = (a, b) => a + b;
const double = number => 2 * number;
const numbers = [1,2,3];

numbers.map(number => 2 * number);

fat arrow functions use lexical 'this'. No longer need to use .bind(this) or var self = this.

const team = {
    members: ['Jane', 'Bill'],
  teamName: 'Super Squad',
  teamSummary: function() {
      return this.members.map((member) => {
        return `${member} is on team ${this.teamName}`;
    });
  }
};

team.teamSummary();

Enchanced Object Literals

Whenever we make reference to a key and a value with the exact same name we can condense to just one name.

If you have a key value pair where the value is a function you can omit both the colon (:) and the function keyword

function createBookShop(inventory) {
    return {
      inventory,
    inventoryValue() {
      return this.inventory.reduce((total, book) => total + book.price, 0);
    },
    priceForTitle(title) {
      return this.inventory.find(book => book.title === title).price;
    }
  };
}

const inventory = [
  { title: 'Harry Potter', price: 10 },
  { title: 'Eloquent Javascript', price: 15 }
];

const bookShop = createBookShop(inventory);

bookShop.inventoryValue();
bookShop.priceForTitle('Harry Potter');
function saveFile(url, data) {
    $.ajax({
    url,
    data,
    method: 'POST'
  });
}

const url = 'http://fileupload.com';
const data = { color: 'red' };

saveFile(url, data);

Default Function Arguments

function makeAjaxRequest(url, method = 'GET') {
  if (!method) {
      method = 'GET';
  }

    // logic to make the request
}

makeAjaxRequest('google.com');
makeAjaxRequest('google.com', 'GET');

Refactored to use default function arguments:

function makeAjaxRequest(url, method = 'GET') {
  return method;
    // logic to make the request
}

makeAjaxRequest('google.com', null);
makeAjaxRequest('google.com', 'GET');
function User(id) {
    this.id = id;
}

function generateId() {
    return Math.random() * 999999;
}

function createAdminUser(user = new User(generateId())) {
    user.admin = true;

  return user;
}

const user = new User(generateId());
createAdminUser(user);

Rest and Spread Operator

function addNumbers(a,b,c,d,e) {
  const numbers = [a,b,c,d,e];
    return numbers.reduce((sum, number) => {
    return sum + number;
  }, 0);
}

addNumbers(1,2,3,4,5);

refactored using rest operator:

function addNumbers(...numbers) {
    return numbers.reduce((sum, number) => {
    return sum + number;
  }, 0);
}

addNumbers(1,2,3,4,5,6,7,8,9,10);

Use rest operator whenever you want to capture a list of data

const defaultColors = ['red', 'green'];
const userFavoriteColors = ['orange','yellow'];
const fallColors = ['fire red', 'fall orange'];

// join two arrays together
defaultColors.concat(userFavoriteColors);

// this does the same thing except more effeciently
[ 'blue', ...fallColors, ...defaultColors, ...userFavoriteColors ];
function validateShoppingList(...items) {
    if (items.indexOf('milk') < 0) {
      return [ 'milk', ...items ];
  }

  return items;
}

validateShoppingList('oranges', 'bread', 'eggs');

A real world example for a math library. Can use rest operator to deprecate older methods:

const MathLibrary = {
    calculateProduct(...rest) {
    console.log('Please use the multiply method instead');
      return this.multiply(...rest);
  },
  multiply(a, b) {
      return a * b;
  }
};

Destructuring

var expense = {
    type: 'Business',
    amount: '$45 USD'
};

// ES5

var type = expense.type;
var amount = expense.amount;

// ES6

const { type, amount } = expense;

Whatever property you're referencing has to be identical to the key in the object.

var savedFile = {
    extension: 'jpg',
    name: 'repost',
    size: 14040
};

function fileSummary({ name, extension, size }) {
    return `The file ${name}.${extension} is of size ${size}`
}

fileSummary(savedFile);

We can destructure arrays as well.

const companies = [
    'Google',
    'Facebook',
    'Uber'
];

const [ name ] = companies;

// this is how it looks with ES5
const firstCompany = companies[0];
const companies = [
    { name: 'Google', location: 'Mountain View' },
    { name: 'Facebook', location: 'Menlo Park' },
    { name: 'Uber', location: 'San Francisco' }
]

const [{ location }] = companies;

The order of the props does not matter when destructuring:

function signup({password, username, email, dob, city}) {
    // create new user
}

const user = {
    username: 'myname',
    password: 'mypassword',
    email: 'myemail@example.com',
    dob: '1/1/1990',
    city: 'New York'
}

signup(user);

Array destructuring example:

// api returns data like this:
const points = [
    [4, 5],
    [10, 1],
    [0, 40]
];

// graphing library expects this structure:
// [
//   { x: 4, y: 5 },
//   { x: 10, y: 1 },
//   { x: 0, y: 40 }
// ]

// use destructing and object literals to format the data
points.map(([x,y]) => {
    return { x, y };
});

Classes

use classes to create objects for inheritance.

Basic example of prototypes in js:

function Car(options) {
    this.title = options.title;
}

Car.prototype.drive = function() {
    return 'vroom';
}

const car = new Car({ title: 'Focus' });

car.drive();
car;

OOP in ES5:

function Car(options) {
    this.title = options.title;
}

Car.prototype.drive = function() {
    return 'vroom';
}

function Toyota(options) {
      Car.call(this, options);
    this.color = options.color;
}

Toyota.prototype = Object.create(Car.prototype);
Toyota.prototype.constructor = Toyota;

Toyota.prototype.honk = function() {
    return 'beep';
}

const toyota = new Toyota({ color: 'silver', title: 'Celica' });

toyota;
toyota.drive();
toyota.honk();

OOP in ES6 using classes:

class Car {
  constructor({ title }) {
      this.title = title;
  }

    drive() {
          return 'vroom';
  }
}

class Toyota extends Car {
  constructor(options) {
          super(options); // Car.constructor()
          this.color = options.color;
  }

    honk() {
          return 'beep';
  }
}

const toyota = new Toyota({ color: 'silver', title: 'Celica' });

toyota.honk();
toyota.drive();
toyota;

When will we actually use classes? A lot of new libraries (React) now use classes.

// ES5
React.createClass({
    doSomething() {

  },

  doSomethingElse() {

  }
});

// ES6
class MyComponent extends Component {
    doSomething() {

  }

  doSomethingElse() {

  }
}

Generators

The 'for of' loop is used for iterating an array of data

const colors = ['red','green','blue'];

for (let color of colors) {
    console.log(color);
}

const numbers = [1,2,3,4];

let total = 0;
for (let number of numbers) {
    total += number;
}

To create a generator function, add an asterisk:

function* numbers() {

}
// or
function *numbers() {

}

A generator is a function that we can enter and exit several times.

function* shopping() {
  // stuff on the street

  // walking down the street

  // go into the store with cash
    const groceries = yield 'cash';

  // walking to laundromat
  const cleanClothes = yield 'laundry'

  // walking back home
  return [ groceries, cleanClothes ];
}

// stuff in the store
const gen = shopping();
gen.next(); // leaving out house
// walked into the store
// walking up and down the aisles
// purchase our groceries

gen.next('groceries'); // leaving the store with groceries
gen.next('clean clothes');

Generators work well with for of loops:

function* colors() {
    yield 'red';
  yield 'blue';
  yield 'green';
}

const myColors = [];

for (let color of colors()) {
    myColors.push(color);     
}

myColors; // ['red','green','blue']

A practical example of using generators with 'for of' loops:

const testingTeam = {
  size: 2,
    lead: 'Amanda',
  tester: 'Bill',
  [Symbol.iterator]: function* () {
      yield this.lead;
    yield this.tester;
  }
};

const engineeringTeam = {
  testingTeam,
    size: 3,
  department: 'Engineering',
  lead: 'Jill',
  manager: 'Alex',
  engineer: 'Dave',
  [Symbol.iterator]: function* () {
      yield this.lead;
    yield this.manager;
    yield this.engineer;
    yield* this.testingTeam;
  }
};

const names = [];

for (let name of engineeringTeam) {
    names.push(name);
}

names; // ['Jill','Alex','Dave','Amanda','Bill']

an example using generators to iterate over a tree of comments:

class Comment {
    constructor(content, children) {
      this.content = content;
    this.children = children;
  }

  *[Symbol.iterator]() {
      yield this.content;
    for (let child of this.children) {
        yield* child;
    }
  }
}

const children = [
    new Comment('good comment', []),
  new Comment('bad comment', []),
  new Comment('meh', [])
];

const tree = new Comment('Great post!', children);

const values = [];
for (let value of tree) {
    values.push(value);
}

values; // ['Great post!','good comment','bad comment','meh']

Promises and Fetch

A promise exists in three states:

  • unresolved - waiting for something to finish
  • resolved - something finished and it all went ok
  • rejected - something finished and something went bad

By default a promise exists in an unresolved state. Then it enters the next possible states which is resolved or rejected.

const url = 'https://jsonplaceholder.typicode.com/posts/';

fetch(url)
  .then(response => response.json())
  .then(data => console.log(data));

Fetch is native to the browser but it does not behave in the same way you would expect. I would recommend using axios, superagent, or jQuery instead.

This was a quick ES6 review from the Udemy class by Stephen Grider. I recommend checking out the class for a more in-depth look at these ES6 topics.

Have Big Dreams?

We can achieve so much more when we work together! Feel free to follow me on social media so we can exchange memes.