skip to content
Notes && Anecdotes
A decorative, semi-related stock photo that adds less value than this comment.

ES6 notes

es6introjavascript

A summary of the key takeaways from CodeSchools “ES2015: The Shape of JavaScript to Come”.

  1. Like in python, ES6 allows for default values in parameters:
function say(name = "Erik") {
	console.log(name + "says hi");
}
  1. You can enter javascript context in the middle of a string with ${javascript}:
function sayHi(name = "Erik") {
	console.log(`${name} says hi`);
}
  1. Passing objects as options has become more pleasing on the eye:
function saySomethingtoSomeone({ toName, fromName, text }) {
	console.log(`${fromName} says:${toName}, ${text}`);
}

saySomethingtoSomeone({
	toName: Hans,
	fromName: Erik,
	text: "Hi",
});
  1. You can also assign default values to those parameters:
function saySomethingtoSomeone({ toName = "Hans", fromName = "Erik", text = "Hi" } = {}) {
	console.log(`${fromName} says:${toName}, ${text}`);
}
  1. Again, like python (sorry, I am bias) ES6 opens for rest parameters, which allows a variable amount of parameters to be passed in its place and interpreted as an array: (Note, rest parameters must be at the end)
function speakTheseWords(speaker, ...words) {
	for (let i in words) {
		console.log(`${speaker}: ${words[i]}`);
	}
}

// OR

function speakTheseWords(speaker, ...words) {
	words.map(function (word) {
		console.log(`${speaker}: ${word}`);
	});
}
  1. You also have arrow functions which can simplify the readability. (Note that this from inside an arrow function refers to the outside-this):
function speakTheseWords(speaker, ...words) {
	words.map((word) => console.log(`${speaker}: ${word}`));
}
  1. Spread operator is the opposite of the rest parameter. Instead of bundling up a bunch of individual parameters into an array (rest parameter), it spreads an array into individual parameters.
let words = ["I", "You", "Love"];
speakTheseWords("Mother of Dragons", ...words);
  1. There’s now a **shorthand for initializing objects **where it assumes the values are equal to the local variable with the same name.
let face = "nice";
let age = "27";

// instead of { face: face, age: age };
let personObject = { face, age };
  1. Similarily, there’s shorthand for deconstructing objects:
let { face, age } = personObject;
  1. Object.assign simplifies merging of objects. This can be helpful in functions accepting many optional parameters in an object, and setting defaults where parameters are missing. Note that the first parameter is changed, and that object parameter n _overrides parameter _n-1 (for n > 2). I.e. below options override defaults which ends up inside an empty object which then is assigned to settings.
let lookAtMe(name, options={}) {
  let defaults = {
    name: 'Peter',
    age: 20,
    weight: '53kg',
    height: '182cm'
  }
  let settings = Object.assign(
    {}, defaults, options)
}
  1. Iterating over arrays with for…of does what **for…in **does in Python (…) and what it sounds like it would (*smirk*):
let arr = [15, 12, 1];
for (let number of arr) {
	// prints 15, 12, 1
	console.log(number);
}
  1. Arrays can also be deconstructed in a similar way as with Objects. Indexes can be skipped by providing no variable name:
let arr = [15, 12, 1];
// a = 15, b = 12, c = 1
let [a, b, c] = arr;
// d = 15, e = 1
let [d, , e] = arr;

// head = 15, tail = [12, 1]
let [head, ...tail] = arr;
  1. Searching in arrays can now be done with Array.find. The function you provide as a parameter loops through the array, and Array.find will return the first value that the provided function returns true from:
let arr = [15, 12, 1];

let val = arr.find(num => {
    return num < 10;
}
// shorthand of the function above
val = arr.find(num => num < 10);

console.log(val);  // prints 1
  1. A new object type Map handles some of the issues that could occur in ES5 when using a plain object as a key-value store. Map should be used instead of a plain object for stores when the key is unknown before runtime.
let user1 = { cake: "good", name: "Huge Jackman" };
let user2 = { cake: "meh", name: "Beef Armstrong" };

let oldStyleBuggyStore = {};
oldStyleBuggyStore[user1] = "Winning";
oldStyleBuggyStore[user2] = "Losing";

// Prints 'Losing'
console.log(oldStyleBuggyStore[user1]);

let newMapType = new Map();
newMapType.set(user1, "Winning");
newMapType.set(user2, "Losing");

// prints 'Winning';
console.log(newMapType.get(user1));
  1. **Iteration of Maps **(unlike objects) can be done in a similar way as with arrays:
for [key, value] in map {
 console.log(`${key}: ${value}`);
}
  1. **Weakmaps **are maps memory-efficient Maps. The difference is:
  2. You cannot iterate over Weakmaps (se point 15)
  3. The keys of Weakmaps may only be Objects (no primitive types)
  4. The key-value pairs of Weakmaps will be removed when the object used as key is garbage collected. This is unlike Maps and Objects KV-stores, where the fact that they’re in the Map/Object prevents the object from being garbage collected.
let tomas = { name: "Tomas", age: 27 };
let memoryEffMap = new WeakMap();
memoryEffMap.set(tomas, "fish");
  1. **Sets **are similar to Arrays, but only have unique values. So while an array can contain 2 instances of the word ‘hat’, Sets will only ever contain 0 or 1 instances.
let tags = new Set();
tags.add("fish");
tags.add("cornbread");
  1. **Weaksets **are to sets as Weakmap are to maps: the keys may only be Objects, it can not be iterated with let…of and it will not prevent garbage collection of its members.
let fish = { title: "fish" };
let cornbread = { title: "cornbread" };

let weakTags = new WeakSet();
weakTags.add(fish);
weakTags.add(cornbread);
weakTags.has(fish); // true;
weakTags.delete(fish); // true
weakTags.has(fish); // false
  1. Class object: ES6 gives you Class, so you can shorten the creation of classes (instead of Prototype).
class Person {
	constructor(firstName, lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	render() {
		document.body.appendChild(`
      <li>
        ${this.firstName} ${this.lastName}
      </li>
    `);
	}
}
  1. Classes can extend other classes. **super **keyword is used to call parent class constructor and other methods.
class Animal extends Person {
	constructor(name, animal, foodToEat) {
		super(name, `the ${animal}`);
		this.food = foodToEat;

		// calls render function defined in person.
		super.render();
	}
}
  1. **Private methods **doesn’t really exist, but (like in Python, heh) the convention is to start the method names with underscore to indicate it shouldn’t be called from outside:
class Person {
	constructor(firstName, lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	render() {
		document.body.appendChild(`<li>${this._getRenderText()}</li>`);
	}

	_getRenderText() {
		return `${this.firstName} ${this.lastName}`;
	}
}
  1. ES6 got modules! Previously, the namespace in javascript was mucky. All files that was loaded on a webpage shared the same namespace. So, for example: Addons to jquery were dependant on jquery being loaded previously. And if you in your modules used a variable ‘cow’, this would be accessable in other javascript files (unless you wrapped them in a function or hid them by other means). In ES6, you can **require **libraries like this:
// messagemodule.js
export function cakeMessage() {
	console.log("cake");
}

export function catMessage() {
	console.log("MIAAAO");
}
// app.js
import { cakeMessage, catMessage } from "./messagemodule";

catMessage(); // prints 'MIAAAO'

There are several ways of exporting and importing functions. Below, what’s exported is not declared before the end of the file. cakeMessage is not exported at all.

// messagemodule2.js
function cakeMessage() {
	console.log("cake");
}
function two() {
	console.log("I'm two years old");
}
function catMessage() {
	console.log("MIAAAO");
}

// Note: cakeMessage not exported
export { two, catMessage };
// app2.js – imports everything from module
import * as msg from "./messagemodule2";

msg.catMessage();
  1. Module **export default: **Default keyword is used when a module only exports one function. This allows for import without the use of curly braces, and being named whatever the importing class wants:
// messagemodule.js
export default function catMessage() {
	console.log("MIAAAO");
}
// app.js
import miao from "./messagemodule.js";
miao();
  1. **Promises **make asynchronous Javascript using callbacks easier to write and read:
function getTasksFromServer() {
  return **new Promise(function(resolve, reject) {**
    let url = '/tasks';
    let request = new XMLRequest();
    request.open('GET', url, true);
    request.onload = function() {
      if (request.status >= 200 && request.status < 400) {
       resolve(JSON.parse(request.response));
      }
    }
  })
}

getTasksFromServer()
  .then(response => renderTasks(response))
);
  1. Then-functions follow promise-calls can also be chained:
getTasksFromServer()
  .then(tasks => tasks.filter(t => t.completed === true))
  .then(completedTasks => renderTasks(completedTasks))
);
  1. Promises can also be rejected, and the error handling is neater than earlier:
function getTasksFromServer() {
  return new Promise(function(resolve, reject) {
    let url = '/tasks';
    let request = new XMLRequest();
    request.open('GET', url, true);
    request.onload = function() {
      if (request.status >= 200 && request.status < 400) {
       resolve(JSON.parse(request.response));
      } else {
        reject(new Error(request.status));
      }
    }
    request.onerror = function() {
      reject(new Error(request.status));
    }
  })
}

getTasksFromServer()
  .then(tasks => tasks.filter(t => t.completed === true))
  .then(completedTasks => renderTasks(completedTasks))
  .catch(error => {
    console.log(`failed with ${error}`);
  });
)
  1. **Iterators **allows you to iterate over elements with **for…of **(see 11). You can add iterators on your own objects. An example of this (not using generators) can be done like this:
function makeIterable(obj) {
	obj[Symbol.iterator] = function () {
		let properties = Object.keys(obj);
		let count = 0;
		let isDone = false;

		let next = () => {
			if (count >= properties.length) {
				isDone = true;
			}
			return { done: isDone, value: obj[properties[count++]] };
		};

		return { next };
	};
}

let cat = { head: 15, tail: 19 };
makeIterable(cat);

for (let [key, val] of cat) {
	console.log(`${key}: ${val}`);
}
  1. Generators are special functions (marked with *) that allow iteration and decomposition. It will have multiple **yield **keywords in it, that specify which values to return on each run, or in each position.
function* names() {
	yield "Tomas";
	yield "Hanna";
	yield "Cookie Monster";
}

// used in iteration
for (let name of names()) {
	console.log(name);
}

let names = [...names()];
  1. Using generators makes creating iterators simpler like this:
function makeIterable(obj) {
  obj[Symbol.iterator] = function* () {
    let properties = Object.keys(obj);
    for (let p of properties) {
      yield [p, obj[p]]
    }
  }
}

let cat { head: 15, tail: 19 };
makeIterable(cat);

for (let [key, val] of cat) {
  console.log(`${key}: ${val}`);
}