Skip to content

Instantly share code, notes, and snippets.

@0bie
Last active November 2, 2016 19:10
Show Gist options
  • Save 0bie/5c43e1e53d9f47a7ba6f65732dc027e9 to your computer and use it in GitHub Desktop.
Save 0bie/5c43e1e53d9f47a7ba6f65732dc027e9 to your computer and use it in GitHub Desktop.
ES6 Notes

Understanding ES6 Concepts

Variables

let

  • Used to declare variables

  • It is block scoped E.g

function order(x, y) {
  if(x > y) {
    let tmp = x;
    x = y;
    y = tmp;
  }
  console.log(tmp === x); //ReferenceError: tmp is not defined
  return [x, y];
}
  • It is mutable (the value can be changed after it has been initialized)

  • When used in a loop let creates a new binding for each loop iteration E.g

// ES5
var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
  (function(index) {
    setTimeout(function() {
      console.log(arr[index]);
    }, 1000);
  })(i);
}
// => 1, 2, 3

// ES6
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  setTimeout(() => console.log(arr[i]), 1000);
}
// => 1, 2, 3

const arr = [];
for (let i = 0; i < 3; i++) {
  arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]

const arr = [];
for (let i of [0, 1, 2]) {
  arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]
  • In the code block above (ES6) each i refers to the binding of one specific iteration and preserves the value that was current at the time. Therefore each arrow function returns a different value

const

  • This works similar to let, one major difference is that it must be immediately initialized E.g
const foo; //SyntaxError: missing = in const declaration
const bar = 123;
bar = 456; //TypeError: 'bar' is read-only
  • Another major difference is that const values are read-only as it is used to define constant values within your code.

  • This means that it creates an immutable binding (the value can't be re-bound after it has been initialized)

  • Using const it is possible to shadow variables within a function E.g

function func() {
  const foo = 5;
  if() {
    const foo = 10; // shadows outer `foo`
    console.log(foo); // 10
  }
  console.log(foo); // 5
}
  • const can also be used to declare objects but an important thing to note is that using const prevents modification of the binding and not of the value itself. E.g
const foo = {
  bar: 'baz'
};

foo.bar = 'foobar' // {bar: foobar}

foo = {           // Uncaught TypeError: Assignment to constant variable
  bar: 'foobar'
};
  • In the code above foo.bar= 'foobar' changes what the const (foo) contains but doesn't change the value that it is bound to. So that line will be valid

  • The line right below that : foo = {bar: 'foobar'} tries to reassign a value to foo (thus attempting to change its binding) and so an error will be thrown

  • const prevents modification of the binding, not modification of the bound value

  • As opposed to var, variables declared via let and const don't become properties of the global object

Temporal Dead Zone (TDZ)
  • Refers to the region before a variable (const let) is defined. This means that if you try to access a variable in the TDZ it will throw a ReferenceError instead of returning undefined like a variable declared with var would E.g
console.log(test1); // undefined
var test1 = 'foo';

console.log(test2); // ReferenceError: cannot access uninitialized variable
const test2 = 'foo';

console.log(test3); // ReferenceError: cannot access uninitialized variable
let test3 = 'foo';
Destructuring
  • You can make use of ES6 Destructuring to extract properties and methods from an object

  • A benefit of this concept is that you can now reference that property or method without using dot notation syntax E.g:

//ES5:
var foo = {
  bar: 1,
  baz: 2
};

console.log(foo.bar); // 1
console.log(foo.baz); // 2

//ES6:

//object
const foo = {
  bar: 1,
  baz: 2
};
const { bar, baz } = foo;
console.log(bar); //1
console.log(baz); //2

//array
const letters = ['A', 'B', 'C'];
const [ firstLetter ] = letters;
console.log(firstLetter); // A
const [,, thirdLetter] = ['A', 'B', 'C'];
console.log(thirdLetter); // C

//loop
const arr1 = ['a', 'b'];
for (const [ index, element ] of arr1.entries()) {
  console.log(index, element); // 0 a  1 b
}

const arr2 = [
  {name: 'Jane', age: 41},
  {name: 'John', age: 31}
];
for (const { name, age } of arr2) {
  console.log(name, age); // Jane 41 John 31
}

//library
import React, { Component, PropTypes } from 'react';
import { each, omit } from 'lodash';
  • One caveat with using destructuring is that they can be used to either declare variables or assign to them, but not both
  • Similar to destructuring this pattern allows reference to an objects' value using a variable if both the name of the variable and the object key are the same
const foo = 2;
const obj = {
  bar: 1,
  foo
}
obj.foo // 2

this.setState({ videos }) === this.setSate({ videos: videos});
//ES5:
function getCar(make, model, value ) {
  return {
    make: make ,
    model: model,
    value: value
  };
}

//ES6:
function getcar(make, model, value) {
  return {make, model, value};
}
Template strings
const VideoDetail = ({video}) => {
  // const video = props.video;

  const videoId = video.id.videoId;
  const url = `https://www.youtube.com/embed/${videoId}`;

  return (
    <iframe className='embed-responsive-item' src={url}></iframe>
  );
}
Classes
//ES5:
function Playlist () {
  this.mediaList = [];
  this.nowPlayingIndex = 0;
}

Playlist.prototype = {
  constructor : Playlist,
  add: function (media) {
    this.mediaList.push(media);
  },
  play: function () {
    var currentMedia = this.mediaList[this.nowPlayingIndex];
    console.log(this.nowPlayingIndex);
    currentMedia.play();
  },
  stop: function () {
    var currentMedia = this.mediaList[this.nowPlayingIndex];
    currentMedia.stop();
  }
}

var playlist = new Playlist;

playButton.addEventListener ('click', function(e) {
  playlist.play();
})

function Country(name, traveled) {
  this.name = name ? name : 'United Kingdom';
  this.traveled = Boolean(traveled);
}

Country.prototype.travel = function() {
  this.traveled = true;
};

var france = new Country('France', false);
france.travel();

var unitedkingdom = new Country;

//ES6:
class Playlist {
  constructor() {
    this.mediaList = [];
    this.nowPlayingIndex = 0;
  }

  play() {
    var currentMedia = this.mediaList[this.nowPlayingIndex];
    console.log(this.nowPlayingIndex);
    currentMedia.play();
  }

  stop() {
    var currentMedia = this.mediaList[this.nowPlayingIndex];
    currentMedia.stop();
  }
}

const playlist = new Playlist();

class PlaylistMod extends Playlist {
  constructor() {
    super() // reference to the constructor in `Playlist` class
  }
  next() {
    this.stop();
    this.nowPlayingIndex++;
    if (this.nowPlayingIndex === this.mediaList.length) {
      this.nowPlayingIndex = 0;
    }
    this.play()
  }
  prev() {
    this.stop();
    this.nowPlayingIndex--;
    if (this.nowPlayingIndex === -1) {
      this.nowPlayingIndex = this.mediaList.length - 1;
    }
    this.play()
  }
}

const playlistMod = new PlaylistMod();

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return `(${this.x}, ${this.y})`;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color;
  }
  toString() {
    return super.toString() + ' in ' + this.color;
  }
}

const cp = new ColorPoint(25, 8, 'green');
cp.toString();  // '(25, 8) in green'
cp instanceof ColorPoint; // true
cp instanceof Point; // true
typeof Point; // function

class Country {
  constructor(name, traveled) {
    this.name = name;
    this.traveled = false;
  }
  travel() {
    this.traveled = true;
  }
}

var france = new Country('France', false);
france.travel();
Arrow functions
  • Arrow functions don't expose the arguments object

  • It automatically binds the lexical context to this within nested functions E.g:

//ES5:
var sum = function(a, b) {
  return a + b;
}

var numbers = [0, 1, 2];
var increment = numbers.map(function(i) {return i++}); // [1, 2, 3]


var module = {
  foo: 'bar',
  baz: function() {
    setTimeout(function() {
      console.log(this.foo)
    }.bind(this), 1000);
  }
}

function UiComponent() {
  var self = this;
  var button = document.getElementById('theButton');
  button.addEventListener('click', function() {
    console.log('CLICK');
    self.handleClick();
  });
}

UiComponent.prototype.handleClick = function() {
  ...
};

const box = document.getElementById('box');
box.addEventListener('click', function() {
  self = this;
  this.classList.toggle('opening');
  setTimeout(function() {
    self.classList.toggle('open');
  }, 500);
});

//ES6:
const sum = (a, b) => {
  return a + b;
}

const sum  = (a, b) => a + 2

const increment = (a) => a++

const arr = [0, 1, 2];
const arrIncrement = arr.map(i => i++); //[1, 2, 3];

const module = {
  foo: 'bar',
  baz: function() {
  setTimeout(() => {
    console.log(this.foo)
  }, 1000);
  }
}

const UiComponent = () => {
  const button = document.getElementById('theButton');
  button.addEventListener('click', function() {
    console.log('CLICK');
    this.handleClick();
  });
}

const box = document.getElementById('box');
box.addEventListener('click', function() {
  this.classList.toggle('opening');
  setTimeout(() => {
    this.classList.toggle('open');
  }, 500);
});

const race = '100m dash';
const winners = ['hunter cash', 'singa song', 'imda bos'];
const result = winners.map((name, i) => ({ // wrap object in parens to indicate an object literal is being returned
  name,
  race,
  position: i + 1
}));

const ages = [23, 44, 60, 80, 90, 30, 70, 65];
const old = ages.filter((age) => age >= 60);
Modules
//module_1.js
module.exports.foo = function() {

};

module.exports.bar = function() {

};

//module_2.js
import module_1 from './module_1';
//or
import { foo as fooo, bar } from './module_1';
Rest parameters and Spread operator
  • Rest properties (or parameters) are used to store the remaining own enumerable property keys that haven't been extracted by the destructuring pattern

  • The keys can then be stored into a new object

  • If the parameter can't find any matching elements it matches its operand (...foo) against the empty array

  • The spread operator is similar, it copies own enumerable properties from a given object into a new created one E.g

//ES6:

//rest
let { x, y, ...z } = {x: 1, y: 2, a: 3, b: 4};
console.log(x); // 1
console.log(y); // 2
console.log(z); // {a: 3, b: 4}

const [x, y, ...z] = ['a']; // x = 'a'; y = undefined; z = []

const [x, ...[y, z]] = ['a', 'b', 'c']; // x = 'a'; y = 'b'; z = 'c'

//spread
let n = { x, y, ...z};
console.log(n); // {x: 1, y: 2, a: 3, b: 4}   
  • The rest parameter is used to extract data while the spread operator is used to insert data into an object

  • More examples:

//ES5:
function logAllArgs() {
  for (var i =0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}

var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];
arr1.push.apply(arr1, arr2); // ['a', 'b', 'c', 'd']

var arr3 = ['a', 'b'];
var arr4 = ['c'];
var arr5 = ['d', 'e'];
console.log(arr3.concat(arr4, arr5)); // ['a', 'b', 'c', 'd', 'e']

function checkSubstrings(string) {
  for (var i = 1; i < arguments.length; i++) {
    if (string.indexOf(arguments[i]) === -1) {
      return false;
    }
  }
  return true;
};

function foo(param1, param1) {
  if (arguments.length < 2) {
    throw new Error('This function expects at least two arguments');
  }
  return arguments;
};

//ES6:
function logAllArgs(...args) {
  for (const arg of args) {
    console.log(arg);
  }
}

const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
arr1.push(...arr2); // ['a', 'b', 'c', 'd']

const arr3 = ['a', 'b'];
const arr4 = ['c'];
const arr5 = ['d', 'e'];
console.log([...arr3, ...arr4, ...arr5]); // ['a', 'b', 'c', 'd', 'e']

function checkSubstrings(string, ...keys) {
  for (var key of keys) {
    if(string.indexOf(key) === -1) {
      return false;
    }
  }
  return true;
}

function foo(...params) {
  if (params.length < 2) {
    throw new Error('This function expects at least two arguments');
  }
  return params;
};
Objects
// return an object
const personInfo = (name, age, jobTitle) => ({
  name,
  age,
  jobTitle
});

// assign the object to a variable
const hodor = personInfo('hodor', 27, 'door keeper');

// log the object
for (const prop in hodor) console.log(`${prop}: ${hodor[prop]}`);
Method definitions in object literals
//ES5:

var obj = {
  foo: function() {
    ...
  },
  bar: function() {
    this.foo();
  }
}

//ES6:

const obj = {
  foo() {
    ...
  },
  bar() {
    this.foo();
  }
}
Merging Objects
  • Object.assign(target, sources);

  • The Object.assign() method can be used to merge the contents of one object into another.

  • The target refers to the object that will receive the content, it could either be an empty literal ({}) or an existing object.

  • The source(s) refers to the object(s) whose content will be extracted and merged.

  • Object.assign() overwrites the content of the target if the target contains the same property E.g

const obj1 = {name: 'Daisy', age: 30};
const obj2 = {name: 'Charles'};

Object.assign(obj1, obj2);
console.log(obj1); // {name: "Charles", age: 30}
console.log(obj2); // {name: "Charles"}
Default Parameters
  • In ES5 we can simulate default parameters for functions using the logical OR operator (||)
// ES5
function foo(param1, param2){
  if (param1 === undefined) {
    param1 = 10;
  }
  if (param2 === undefined) {
    param2 = 10;
  }
  console.log(param1, param2);
}

foo(5, 5); // 5 5
foo(5); // 5 10
foo(); // 10 10
foo(0, null); // 0, null
  • In ES6 we no longer need to check for undefined values within our function with default parameters
//ES6
function foo(a = 10, b = 10) {
  console.log(a, b);
}

foo(5); // 5 10
foo(0, null); // 0 null
  • Pass a function (getParam()) as the default parameter in multiply()
function getParam() {
  const number = prompt('Enter a number');
  return parseInt(number);
}

function multiply(param1, param2 = getParam()) {
  return param1 * param2;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment