A talk by Christophe Porteneuve at Voxxed Days Thessaloniki 2016
const christophe = {
ηλικία: 38.9637234770705,
οικογένεια: { γυναίκα: 'Élodie', υιός: 'Maxence' },
τοποθεσία: 'Paris, FR',
εταιρεία: 'Delicious Insights',
σεμινάρια: ['360° JS', 'Node.js', '360° Git'],
στοWebΑπό: 1995,
ΔιασημοςΓια: [
'Prototype.js',
'Ruby On Rails',
'Prototype and Script.aculo.us',
'Paris Web'
]
}
May 1995 | JavaScript 1.0 (in 10 days). Netscape 2.0ß3 on 12/95. |
June 1997 | ECMAScript (ES1, ECMA-262), official standard. |
Dec. 1999 | ES3. JScript 1.5 about on par (IE4–8) |
Dec. 2009 (!) | ES5. Compatibility baseline for now. IE9+, Node, etc. Few big changes. |
June 2015 | ES2015 / ES6. Huge changes to the language itself. |
June 2016 | ES2016 / ES7. Few big changes. But wait for ES2017! |
Great compat table by Juriy “Kangax” Zaytsev.
Evergreens 90–98%. Safari 10+ too.
Node 4 = 48%, Node 5 = 58%, Node LTS 6 = 96%!
Sure, you may need to deal with IE (9–11), and even 58% isn’t quite enough to be serene. So what do we do then?
Transpiles ES2015+1 (ES2015, ES2016, ES2017…) to ES5
This way, if you have ES5, you’re golden
In practice: IE9+, evergreens, all Node.js / io.js
Integrates with everything
Easier
More powerful
More reliable
More performant
res.render('entries/index', {
pageTitle: 'Bookmarks', entries, entryCount, tags
})
const coreReducer = combineReducers({
currentUser, goals, history, today, todaysProgress
})
return { [action.goalId]: previous + increment }
class TodoItem extends Component {
constructor (props, context) {
super(props, context)
this.state = {
editing: false
}
}
handleDoubleClick () {
this.setState({ editing: true })
}
…
}
class TodoItem extends Component {
static MINIMUM_COMPLETION_TIME = 5
get completionTime () {
return (this.checkedAt - this.startedAt) / 1000
}
set completionTime (value) {
this.checkedAt = this.startedAt + Math.max(value, MINIMUM_COMPLETION_TIME) * 1000
}
cleanUp () {
super.cleanUp()
this.customState = null
}
}
const { filter: selectedFilter, onShow } = this.props
const [, filters] = output.props.children
var { op: a, lhs: { op: b }, rhs: c } = getASTNode()
function convertNode ({ nodeType, nodeValue }) {
switch (nodeType) {
case ELEMENT_NODE:
convertElementNode(nodeValue)
break
// …
}
}
const [
endpoint = '/users/me',
output = 'response.json'
] = process.argv.slice(2)
const HistDayGoal = ({ goal: { name, units }, stats: [progress, target] }) => {
// …
}
Promise.all([
Entry.tags(), Entry.count().exec(), Entry.getEntries(req.query)
])
.then(([tags, entryCount, entries]) => {
res.render('entries/index', { title: 'Bookmarks', entries, entryCount, tags })
})
function winners(boss, realBoss, ...contenders) {
console.log(boss, realBoss, contenders)
}
winners('Ζεύς', 'Ἥρα', 'Μῆτις', 'Θέμις', 'Λητώ')
// => 'Ζεύς', 'Ἥρα', ['Μῆτις', 'Θέμις', 'Λητώ']
var arr1 = ['one', 'two'], arr2 = ['three', 'four']
arr1.push(...arr2) // => 4, arr1 is ['one', 'two', 'three', 'four']
var helen = { rel: 'Μενέλαος', loc: 'Σπάρτη', mother: 'Λήδα', stable: true }
var kidnapping = { rel: 'Πάρις', loc: 'Τροία' }
var trouble = { ...helen, ...kidnapping, stable: false }
// => { rel: 'Πάρις', loc: 'Τροία', mother: 'Λήδα', stable: false }
var nodeListAsArray = [...document.querySelectorAll('h1')]
return [{ id: …, completed: …, text: … }, ...state]
function todos (state = initialState, action) {
// …
}
function convertNode ({ nodeType = ELEMENT_NODE, nodeValue }) {
// …
}
var [first, second = 'foo'] = ['yo']
// second === 'foo'
var person = { first: 'Louis', sex: 'M', age: 36 }
var { first, last = 'Doe', age = 42 } = person
// first === 'Louis', last === 'Doe', age === 36
var person = { first: 'Thomas', last: 'Anderson', age: 25, nickname: 'Neo' }
// Any-expr interpolation
console.log(`${person.first} aka ${person.nickname}`)
// => 'Thomas aka Neo'
// Multiline!
var markup = `<li>
${person.first} ${person.last}, age ${person.age}
</li>`
let
& const
const { activeCount } = this.props
…
const { filter: selectedFilter, onShow } = this.props
const ADD_TODO = 'ADD_TODO'
const DELETE_TODO = 'DELETE_TODO'
const EDIT_TODO = 'EDIT_TODO'
Scope: the block (even implicit loop block)
for (let index = 0; index < 10; ++index)
setTimeout(() => console.log(index), 10)
// => Indeed 0 through 9, not 10 x 10, despite delayed closure
“
. const
is the new var
”let
only if need proven.
const
≠ immutableWarning! const
isn’t recursive/deep freezing!
const author = { name: 'Bob', language: 'js' }
author.language = 'es6'
const days = ['Tuesday', 'Wednesday', 'Thursday']
days.push('Friday')
To deep-freeze, use deep-freeze.
Octal and binary (052
wouldn’t work in strict mode anyway)
0o52 // => 42
0b101010 // => 42
MOAR Unicode
"𠮷".length == 2 // Char count, not codepoint count
"\u{20BB7}" == "𠮷" == "\uD842\uDFB7" // New escape syntax for full codepoints
"𠮷".match(/./u)[0].length == 2 // New 'u' flag for regexps, changes classes
"𠮷".codePointAt(0) == 0x20BB7 // New codePointAt API (adds to charCodeAt)
Doesn’t redefine this
*, so it stays lexical!
{goals.map((goal) =>
<GoalSetting key={goal.id} goal={goal}
onDeleteClick={() => this.openGoalDeleter(goal)}
onEditClick={() => this.openGoalEditor(goal)}
/>
)}
const mapStateToProps = ({ goals, currentUser }) => ({ goals, currentUser })
Ideal for predicates, filters, transforms…
[12, 18, 21, 23].map((hour) => [hour, 0, 0])
return state.map((goal) => goal.id === newGoal.id ? newGoal : goal)
2 new types of collections.
const s = new Set()
s.add('es2015').add('trashes').add('es5').add('es2015')
s.size // => 3
s.has('es2015') // => true
const items = ['es2015', 'trashes', 'es5', 'es2015']
[...new Set(items)] // => ['es2015', 'trashes', 'es5']
const m = new Map()
m.set('42', 'foobar')
m.get('42') // => 'foobar'
m.get(42) // => undefined
m.has(42) // => false
m.size // => 1
Garantees: unique, exclusive call (success/failure)
Exception capturing
Composability
Tons of libs pre-ES2015, jqXHR, recent Web APIs…
(ServiceWorker, Fetch, Streams, Push, Notification, Battery, etc.)
then
somePromise.then(value => processing)
// Processing can return a fixed value: turned into a fulfilled promise…
getJSON('/meta').then((res) => res.user_url)
// …or return another promise, inserted in the chain
getJSON('/').then((res) => getJSON(res.user_url)).then(console.log)
Actually 2 arguments: callbacks for success and failure
getJSON('/meta')
.then((res) => getJSON(res.user_url))
.then(console.log, console.error)
.then(releaseConnection)
As soon as an error is consumed, it’s neutralized
(just like with try…catch
)
catch
Beware: same-level callbacks are mutually exclusive.
getJSON(res.user_url).then(
(res) => res.fouleNayme.split('/'), // Blam!
console.error // Never called
)
getJSON(res.user_url).then((res) => res.fouleNayme.split('/')) // Blam!
.then(console.log, console.error) // console.error called!
getJSON(res.user_url).then((res) => res.fouleNayme.split('/'))
.then(console.log)
.catch(console.error)
A great playground: Promisees
async
/ await
async function chainAnimationsAsync (elem, animations) {
let ret = null
try {
for (const anim of animations) {
ret = await anim(elem)
}
} catch (e) { /* ignore and keep going */ }
return ret
}
async
/ await
async function processUserNotebook (request, response) {
try {
const user = await User.get(request.user)
const notebook = await Notebook.get(user.notebook)
response.send(await doSomethingAsync(user, notebook))
} catch (err) {
response.send(err)
}
}
async
/ await
async function listEntries (request, response) {
const [tags, entryCount, entries] = await Promise.all([
Entry.tags(), Entry.count().exec(), Entry.getEntries(req.query)
])
res.render('entries/index', { title: 'Bookmarks', entries, entryCount, tags })
}
Langage level; no circular dependencies thanks to live bindings.
|
|
// Full import, Node namespace style:
import * as types from '../constants/ActionTypes'
// Named import of the default export, plus homonymous named exports
import React, { PropTypes, Component } from 'react'
// Or just one case at a time:
import classnames from 'classnames'
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
// Renamed import:
import { makeHigherOrder as wrap } from 'toolkit'
An asynchronous loading API for when we need it…
System.import('toolkit')
.then(toolkit => …)
.catch(error => …)
// Many modules? Combine promises!
Promise.all(['toolkit', 'security', 'advanced'].map(System.import))
.then(([toolkit, security, advanced]) => …)
.catch(error => …)
Possible intercepts of any interaction with an object.
new
, call…You can do anything!… but non-emulable with Babel.
Edge 12+, Chrome 49+, Firefox 38+, Node 6+.
The gist: you build a proxy that wraps a target object.
You define traps on it for every intercept you need. The Reflect
API provides built-in, default implementations.
function wrapAsDefensive (obj) {
return new Proxy(obj, {
get (target, property, receiver) {
if (!(property in target)) {
throw new ReferenceError(
`Property ${property} missing in ${JSON.stringify(target)}`)
}
return Reflect.get(target, property, receiver)
}
})
}
ES.Next facilitates higher-order components with this. Beware though, back to stage 1 (drawing board)…
@autobind
class TodoItem extends Component {
@memoize('5min')
getOverallCounts () {
// …
}
@override
render () {
// …
}
}
ES6 In Depth (MDN)
ES6 In Depth (Nicolás “Ponyfoo” Bevacqua)
Exploring ES6 (Dr. Axel Rauschmayer)
Understanding ES6 (Nicholas Zakas)
lebab (née xto6)
Christophe Porteneuve
Slides are at bit.ly/voxxed-es2015