ECMAScript 6 (2015)
[notes, exercises]
babel src/foo.js > dist/foo.js
Classes
function Something(...) { ... }
Something.prototype =
  Object.create(Super)
Something.prototype.foo = function() { ... }
Something.prototype.bar = function() { ... }

This isn't great

class Something extends Super {
  constructor(...) { ... }
  foo() { ... }
  bar() { ... }
}

I'd say this is better

class Person {
  get age() {
    return this._age - 5
  }
  set age(val) {
    this._age = val + 5
  }
}
class Date {
  static now() { ... }
}

console.log(Date.now())
function Class(a, b) {
  Super.call(this, a, b)
}
Class.prototype.method = function() {
  if (something)
    Super.prototype.method.call(this)
}

This is not super

class Class extends Super {
  constructor(a, b) {
    super(a, b)
  }
  method() {
    if (something) super.method()
  }
}

This is okay

var MyClass = mixin(class extends Super {
  ...
})

Class expressions

Object literals
obj = {
  method1(x) { return x + 1},
  method2(y) { return y - 1}
}
obj = {x, y}
// is equivalent to
obj = {x: x, y: y}
obj = {__proto__: null}
// is equivalent to
obj = Object.create(null)
propName = "onclick"
obj = {
  foo: 10,
  [propName](e) { e.preventDefault() }
}
Block scope
let x = 5
{
  let x = 10
  // local x is 10
}
// our old x is still 5
const x = 5
x = 6
// x is still 5

Constants

if (condition) {
  console.log(localFunc())
  function localFunc() { return "hi" }
}
// localFunc does not exist here

Hoisted local functions

for (let i = 0; i < a.length; i++)
  console.log(a[i])

New scope for each iteration

Arrow functions
f = function(x) { return x + 1 }

18 characters of boilerplate

f = x => x + 1

2 characters of boilerplate

f = x <= x + 1
while (x --> 0) { ... }

Not to be confused with

f = () => 1
f = (x) => x + 1
f = (x, y) => x + y
array.filter(x => x < 100).map(x => x * .01)

setTimeout(() => alert("BOO"), 500)

n.addEventListener("click", e => this.click(e))

More one-liners!

this.click??
array.forEach(elt => {
  let elt2 = elt * elt
  if (elt2 > 100)
    console.log(elt, elt2)
})

Braces create a body

array.map(elt => {
  x: elt,
  y: 2,
  z: true
})

Braces don't create an object!

array.map(elt => ({
  x: elt,
  y: 2,
  z: true
}))

Wrap them in parentheses

Destructuring
let [x, y] = [1, 2]

Array patterns

let {x: x, y: y} = {x: 1, y: 2}
// equivalent to
let {x, y}       = {x: 1, y: 2}

Object patterns

let {body: {lastChild: {nodeName, nodeType}}}
  = document

Nest as deep as you like

let {x = 0, y = 0} = {x: 1}

Defaults

let   [x, y] = [1, 2]
const [x, y] = [1, 2]
var   [x, y] = [1, 2]

In any binding construct

[x, y] = [1, 2];
({x, y} = {x: 1, y: 2})

Direct assignment

function length({x, y}) {
  return Math.sqrt(x * x + y * y)
}

In function arguments

function indexOf(arr, elt, start=0) {
  ...
}

Argument default values

let {x: {y}} = {z: 10}
// BOOM Cannot read property 'y' of undefined

Destructuring can fail

Spread
let x = [1, 2]
let y = [...x, 10, ...x]
// y is [1, 2, 10, 1, 2]

No more .concat().concat().concat()

let [a, b, ...rest] = [1, 2, 3, 4]
// rest is [3, 4]

Use it in patterns too!

function printf(format, ...args) {
  ...
}

Get a (real) array of arguments

let nums = [4, 10, 2]
console.log(Math.max(5, ...nums))

More convenient than .apply

Template strings
`I am a string`

(Really, it is)

`these are\nthree
lines`

(Escapes and newlines!)

`Hello ${name}`

(Interpolation?!)

String.raw`^\w\S*`
// → "^\\w\\S*"

Prevent escape interpretation

foo`one ${two} and ${three}`
// is equivalent to
foo(["one ", " and ", ""], two, three)
Map & Set
let names = new Map
names.set(Array, "the array constructor")
names.set(Math, "the math object")
names.get(Array) // → "the array constructor"

Re-vo-lutionary (?)

map.size
map.set(key, value)
map.get(key)
map.delete(key)
map.has(key)
map.clear()
set.size
set.add(key)

set.delete(key)
set.has(key)
set.clear()
Promises
jQuery.get("/data").then(function(text) {
  return jQuery.get("/user/" + text)
}).then(function(text) {
  console.log(text)
})
function file(path) {
  return new Promise((succeed, fail) => {
    fs.readFile(path, "utf8", (error, content) => {
      if (error) fail(error)
      else succeed(content)
    })
  })
}
Symbols
// Player A
Array.prototype.search =
  function(element, start, end) { ... }

// Player B
Array.prototype.search =
  function(predicate) { ... }

Not going to end well

const search = Symbol("search")
Array.prototype[search] =
  function(element, start, end) { ... }

array[search]("x", 2, 6)
class Everything {
  static [Symbol.hasInstance](obj) {
    return true
  }
}
Iterators
iterator.next() // → {value, done}

Iterator interface

class Iterable {
  [Symbol.iterator]() { return ... }
}

Iterable interface

for (let elt of array) {
  ...
}

For/of loops over an iterable

for (let char of string) {
  ...
}

Strings are also iterable

for (let [key, value] of map) {
  ...
}

As are Map and Set

[... "foo"]
// → ["f", "o", "o"]

Spread operator accepts any iterable

Generators
function forEach(array, f) {
  for (let i = 0; i < array.length; i++)
    f(array[i])
}

Take an internal iterator

function* forEach(array) {
  for (let i = 0; i < array.length; i++)
    yield array[i]
}

Turn it into an external one

function* integers(from, to) {
  for (let i = from; i <= to; i++) yield i
}

for (let i of integers(100, 200))
  console.log(i)

Didn't for/of take an iterable?

function Iterator {}
Iterator.prototype =
  Object.getPrototypeOf(
    Object.getPrototypeOf(
      [][Symbol.iterator]()))
class Tree {
  constructor(value, left, right) { ... }

  *[Symbol.iterator]() {
    if (this.left) yield* this.left
    yield this.value
    if (this.right) yield* this.right
  }
}
function countDown(n) {
  return {
    next(add = 0) {
      n += add - 1
      if (n < 0) return {done: true}
      else return {value: n, done: false}
    }
  }
}

Resettable countdown

function* countDown(n) {
  while (n >= 0) n += (yield --n) || 0
}

Resettable countdown

function* getUser(name) {
  let data = JSON.parse(yield get("/u/" + name))
  if (data.avatar)
    data.pic = yield get("/i/" + user.avatar)
  return data
}
drive(function*() {
  let user = yield* getUser(USERNAME)
  let friends = []
  for (let friend of user.friends)
    friends.push(yield* getUser(friend))
  renderUser(user, friends)
})

Render the user

function drive(generator) {
  let iterator = generator()
  function resume(result) {
    if (result.done) return
    result.value.then(
      value => resume(iterator.next(value)),
      error => resume(iterator.throw(error)))
  }
  resume(iterator.next())
}
Modules
export let pi = 3
export function area(r) { return pi * r * r }
export class Point { ... }

Exporting is easy

export default 42

Default export

import {pi, area} from "./math"

Importing is also easy

import fourtyTwo from "./42"

Importing the default export

import {pi as three} from "./math"
console.log(three)

You can rename imports

import * as math from "./math"
console.log(math.pi)

Or wrap them in an object

export {pi as three, area} from "./math"

Re-export

export {pi, area}

Export after the fact

System.import("./math").then(mod => ...)

Dynamic run-time import (?)

Modules
let bytes = new Uint8Array(1024)
bytes.fill(16, 512, 1024)
console.log(bytes[0], bytes[512])
// → 0 16

Compact typed arrays

Array.from(arrayLike)
Array.of(1, 2, 3)
array.copyWithin(0, 2, 4)
array.fill(1, 1, 3)
find(x => x > 10)
findIndex(x => x > 10)

More array methods

isNaN(undefined)        // → true
Number.isNaN(undefined) // → false

Cleaned-up number functions

parseInt("011")         // → 9
Number.parseInt("011")  // → 11

Cleaned-up number functions

/[😀-😚]/.test("😂") // → ERROR
/[\ud83d\ude00-\ud83d\ude1a]/.test("😂") // → false
/[😀-😚]/u.test("😂") // → true

RegExp.unicode

let s = String.fromCodePoint(0x1f602) // → "😂"
s.length                           // →      2
s.charAt(0)                        // →  55357
s.codePointAt(0)                   // → 128514
"x".repeat(10) // → "xxxxxxxxxx"

Advanced stuff

eloquentjavascript.net
marijnhaverbeke.nl