Une présentation de Christophe Porteneuve à Nocturnes FedeRez 2016
const christophe = {
family: { wife: 'Élodie', son: 'Maxence' },
city: 'Paris, FR',
company: 'Delicious Insights',
trainings: ['JS Total', 'Node.js', 'Git Total'],
jsSince: 1995,
claimsToFame: [
'Prototype.js',
'script.aculo.us',
'JS Attitude',
'Bien Développer pour le Web 2.0',
'NodeSchool Paris'
]
}
| mai 1995 | JS 1.0 (en 10 jours). Netscape 2.0ß3 en 12/95. |
| juin 1997 | ECMAScript (ES1, ECMA-262), standard officiel |
| déc. 1999 | ES3. JScript 1.5 à peu près à ce niveau (IE4–8) |
| déc. 2009 (!) | ES5. Baseline de compat’ actuelle. IE9+, Node, etc. Peu de gros changements. |
| juin 2015 | ES2015 / ES6. Énormément de nouveautés de langage pur. |
| juin 2016 | ES2016 (versions annuelles désormais, dans le cadre d’ES.Next). Peu de choses. |
| juin 2017 | ES2017. async/await, trailing commas, Rest/Spread properties, async iterators… |
La table de compat’ de Juriy “Kangax” Zaytsev.
Evergreens de 90% à 100%.
Node 4.x à 52%, Node 5.x à 58%, Node LTS 6 à 99% !
Évidemment il reste IE (9–11), et puis même 58% ça reste insuffisant tel quel pour être serein·e… Alors on fait quoi, si on doit gérer ça ?
Transpile ES6+1 (ES6, ES7…) en ES5
Du coup, si tu as ES5, tu peux y aller
En pratique : IE9+, evergreens, tous les Node.js / io.js
Y’a même une partie qu’on pourrait transpiler en ES3… o_O
Intégré avec l’univers
Plus facile
Plus puissant
Plus fiable
Plus performant
res.render('entries/index', {
pageTitle: 'Les 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
// obj.completionTime
get completionTime () {
return (this.checkedAt - this.startedAt) / 1000
}
// obj.completionTime = …
set completionTime (value) {
this.checkedAt = this.startedAt + Math.max(value, MINIMUM_COMPLETION_TIME) * 1000
}
cleanUp () {
super.cleanUp()
this.customState = null
}
}
const { activeCount } = this.props
…
const { filter: selectedFilter, onShow } = this.props
const [totalProgress, totalTarget] = getDayCounts(todaysProgress, goals)
…
const [, msgList, msgPane] = output.props.children
function convertNode ({ nodeType, nodeValue }) {
switch (nodeType) {
case ELEMENT_NODE:
convertElementNode(nodeValue)
break
// …
}
}
const [
endpoint = '/users/me',
output = 'response.json'
] = process.argv.slice(2)
const HistoryDayGoal = ({ 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', { pageTitle: 'Les bookmarks', entries, entryCount, tags })
})
function winners(first, runnerUp, ...others) {
console.log(first, runnerUp, others)
}
winners('alice', 'bob', 'claire', 'david')
// => 'alice', 'bob', ['claire', 'david']
var arr1 = ['one', 'two']
var arr2 = ['three', 'four']
arr1.push(...arr2) // => 4
arr1 // => ['one', 'two', 'three', 'four']
var nodeListAsArray = [...document.querySelectorAll('h1')]
var defaults = { first: 'John', last: 'Doe', age: 42 }
var trainer = { last: 'Smith', age: 35 }
trainer = { ...defaults, ...trainer, age: 36 }
// => { first: 'John', last: 'Smith', age: 36 }
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' }
// Interpolation de JS quelconque
console.log(`${person.first} aka ${person.nickname}`)
// => 'Thomas aka Neo'
// Multi-ligne !
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'
Portée : le bloc (même implicite)
for (let index = 0; index < 10; ++index)
setTimeout(() => console.log(index), 10)
// => Effectivement de 0 à 9, pas 10 x 10, malgré l’asynchrone
“
. const is the new var”let seulement si besoin.
const ≠ immutableAttention ! const n’est pas récursif / profond !
const author = { name: 'Bob', language: 'js' }
author.language = 'es6'
const days = ['tuesday', 'wednesday', 'thursday']
days.push('friday')
Pour geler en profondeur, voir deep-freeze.
Octaux et binaires (052 ne marche plus en strict)
0o52 // => 42
0b101010 // => 42
Encore plus d’Unicode
"𠮷".length == 2 // Nombre de caractères, pas de codepoints
"\u{20BB7}" == "𠮷" == "\uD842\uDFB7" // Nouvelle syntaxe d’échappement pour codepoint
"𠮷".match(/./u)[0].length == 2 // Nouveau flag 'u' des regexp, change les classes
"𠮷".codePointAt(0) == 0x20BB7 // Nouvelle API codePointAt (en plus de charCodeAt)
Ne redéfinit pas this*, qui reste donc lexical !
{goals.map((goal) =>
<GoalSetting key={goal.id} goal={goal}
onDeleteClick={() => this.openGoalDeleter(goal)}
onEditClick={() => this.openGoalEditor(goal)}
/>
)}
const mapStateToProps = ({ goals, currentUser }) => ({ goals, currentUser })
Raccourcis possibles, idéal pour les prédicats, filtres, transfos…
[12, 18, 21, 23].map((hour) => [hour, 0, 0])
return state.map((goal) => goal.id === newGoal.id ? newGoal : goal)
2 nouveaux types de collections.
const s = new Set()
s.add('es6').add('défonce').add('es5').add('es6')
s.size // => 3
s.has('es6') // => true
const items = ['es6', 'défonce', 'es5', 'es6']
[...new Set(items)] // => ['es6', 'défonce', 'es5']
const m = new Map()
m.set('42', 'foobar')
m.get('42') // => 'foobar'
m.get(42) // => undefined
m.has(42) // => false
m.size // => 1
Garanties d’appel unique et exclusif
Capture des exceptions / erreurs
Composabilité
Plein de libs pré-ES6, jqXHR, APIs web récentes…
(ServiceWorker, Fetch, Streams, Push, Notification, Battery, etc.)
then
somePromise.then(value => processing)
// Le traitement peut renvoyer une valeur fixe, qui sera promesse accomplie
getJSON('/meta').then(res => res.user_url)
// …ou renvoyer une autre promesse, qui s'incruste dans la chaîne
getJSON('/')
.then(res => getJSON(res.user_url))
.then(console.log)
Il y a en fait 2 arguments : les rappels de succès et d’erreur
getJSON('/meta')
.then(res => getJSON(res.user_url))
.then(console.log, console.error)
.then(releaseConnection)
Dès qu’une erreur est consommée, elle est neutralisée
(comme un try…catch)
catchAttention à ne pas confondre les niveaux : 2 callbacks de même niveau sont mutuellement exclusifs.
getJSON(res.user_url).then(
res => res.fouleNayme.split('/'), // Erreur !
console.error // Jamais appelée
)
getJSON(res.user_url)
.then(res => res.fouleNayme.split('/')) // Erreur !
.then(console.log, console.error) // console.error appelée !
then(null, errorHandling) c’est très moche…
getJSON(res.user_url)
.then(res => res.fouleNayme.split('/'))
.then(console.log)
.catch(console.error)
Super manière de les apprivoiser : 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 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 (suite)
async function listEntries (request, response) {
try {
const [tags, entryCount, entries] = await Promise.all([
Entry.tags(),
Entry.count().exec(),
Entry.getEntries(req.query)
])
res.render('entries/index', { pageTitle: 'Les bookmarks', entries, entryCount, tags })
} catch (err) {
req.flash('error', `Impossible d’afficher les bookmarks : ${err.message}`)
res.redirect('/')
}
}
Niveau langage, donc analysables statiquement ; pas de soucis de dépendances circulaires grâce à l’export de live bindings.
Mode strict par défaut (comme les corps de classes).
|
|
// Import intégral dans un « namespace »
import * as types from '../constants/ActionTypes'
// Import nommé de l’export par défaut, imports homonymes d’exports nommés
import React, { PropTypes, Component } from 'react'
// Ou juste un des deux modes :
import classnames from 'classnames'
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
// Import renommé
import { makeHigherOrder as wrap } from 'toolkit'
ES.Next facilite les higher-order components avec ce sucre syntaxique. Aujourd’hui stade 2 (ES2018 sans doute)
@deprecated('Use pure-functional version instead.')
class TodoItem extends Component {
@memoize('5min')
@autobind
getOverallCounts () {
// …
}
@override
render () {
// …
}
}
ES6 In Depth (MDN)
ES6 In Depth (Nicolás Bevacqua)
lebab (ex-xto6)