babel src/foo.js > dist/foo.js
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
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() } }
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
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
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
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
`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)
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()
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) }) }) }
// 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 } }
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
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()) }
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 (?)
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