On Github turnbullm / javascript-2015-presentation
#1
var title = ['Welcome', 'To', 'Javascript', '2015'] .join(' ') .toLowerCase()
#2
define "title" = create Array("Welcome", "To", "Javascript", "2015") then, implode with ' ' then, stringToLower
var title = ['Welcome', 'To', 'Javascript', '2015'] .join(' ') .toLowerCase()
Who decided this is Javascript?
Not much help...
Finalized in 2009
Object.keys() Date.toISOString() Date.now() JSON.parse() JSON.stringify() String.trim() Array.isArray() Array.indexOf() Array.forEach() Array.map() Array.filter()
Let's fast forward...
Start using it now, or be left behind!
These are all popular and influential frameworks
They are all using ES6 now!
What about browser support?
November 2015
IE10 7% Safari 6 12% IE11 16% Safari 7 & 8 21% Safari 9 54% Chrome 65% Firefox 74% IE Edge 84%Support will improve over time
Support will improve over time
fancyES6Code();
regularES5Code();
fancySassCode
regularCssCode
> babel main.js
// main.js require("babel-core/register"); // ES6 here
<script src="babel-core/browser.js"></script> <script src="main.es6.js"></script>
.pipe(babel())
grunt.initConfig({ "babel": {} })
24 new features
var myBestFriend = 'Barry'; if (true) { var myBestFriend = 'Anthony'; } console.log( myBestFriend ); // ???
var myBestFriend = 'Barry'; if (true) { var myBestFriend = 'Anthony'; } console.log( myBestFriend ); // Anthony
var numbers = [1, 5, 10]; // Add numbers together var result = 0; for (var i = 0; i < numbers.length; i++) { var number = numbers[i]; result += number; } console.log( result ); // 16 console.log( i ); // 3 console.log( number ); // 10
Limits variable to block scope
let numbers = [1, 5, 10]; let result = 0; for (let i = 0; i < numbers.length; i++) { let number = number[i]; result += number; } console.log( result ); // 16 console.log( i ); // undefined console.log( number ); // undefined
This is how most would expect var to behave
Use let instead of var
(unless you really need var scoping)
Value that cannot be altered
const myConstant = 'never changes'; myConstant = 'can I change it?'; // NO!
Use const instead of let
Guarantees references won't be overridden
(if you need references overriden, then use let)
const testString = 'testing'; testString.startsWith('t'); // true testString.startsWith('test'); // true testString.startsWith('e', 1); // true testString.startsWith('est', 1); // true testString.endsWith('ing'); // true testString.endsWith('tin', 1); // true testString.includes('sti'); // true
Converts objects to an array
const myArray = Array.from(notAnArray);
Works with either;
Array.from(document.querySelectorAll('img')); Array.from(jQuery('img')); Array.from("test"); // ["t", "e", "s", "t"]
const friends = [ { name: 'Barry', species: 'Bear' }, { name: 'Anthony', species: 'Anteater' } ]; const myBestFriend = friends.find(function(friend) { return friend.name === 'Barry'; }); console.log( myBestFriend ); // { name: 'Barry', species: 'Bear' } const myBestFriendIndex = friends.findIndex(function(friend) { return friend.name === 'Barry'; }); console.log( myBestFriendIndex ); // 0
const sentence = joinWords('I', 'love', 'Barry');
function joinWords(word1, word2, word3) { // Join the words and return them }
function joinWords() { const words = Array.prototype.slice.call(arguments); // Join the words and return them }
const words = ['I', 'love', 'Barry'];
// Could try alter joinWords function to accept an array? const sentence = joinWords(words);
const sentence = joinWords.apply(null, words);
Allows expressions to be expanded into arguments
Represented by 3 dots
const words = ['I', 'love', 'Barry']; const sentence = joinWords(...words); const sentence = joinWords('I', 'love', 'Barry'); // Same thing!
function joinWords(...words) { // 'words' is an array! }
const sentence = joinWords(...['I', 'love', 'Barry']);
const words = ['I', 'love']; const sentence = joinWords(...words, 'my', ...['friend', 'Barry']);
const words = ['I', 'love', 'Barry']; const wordsCopy = [...words];
const position = [50, 200];
const x = position[0]; const y = position[1];
const [x, y] = position;
Works with spreads
const [a, b, ...rest] = [1, 2, 3, 4, 5]; console.log( rest ); // [3, 4, 5]
Works even better with objects
function getPosition() { return { x: 50, y: 200, z: 13 }; }
const { x, y } = getPosition();
const { z : zIndex } = getPosition(); console.log( zIndex ); // 13
function getPosition(something) { const x, y, z; // Do some calculation magic // Populate x, y, z constants // ES5 return { x: x, y: y, z: z }; // ES6 return { x, y, z }; }
const name = 'Bob'; const activity = 'running'; const place = 'library';
// ES5 const sentence = name + ' is ' + activity + ' to the ' + place;
// ES6 const sentence = `${name} is ${activity} to the ${place}`;
// ES5 const story = '"My name is ' + name + '" said ' + name + '.\n' + '"I\'m ' + activity + ' to the ' + place + '", he continued.\n' + 'Then ' + name + ' went ' + activity + ' to the ' + place + '.';
// ES6 const story = `"My name is ${name}" said ${name}. "I'm ${activity} to the ${place}", he continued. Then, ${name} went ${activity} to the ${place}.`;
function displayMessage(message) { // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
Stop duplicate messages from displaying
function displayMessage(message) { // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = []; function displayMessage(message) { // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = []; function displayMessage(message) { messagesDisplayed.push(message); // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = []; function displayMessage(message) { messagesDisplayed.push(message); // Some magic here to display the message } function removeMessage(message) { messagesDisplayed.splice(messagesDisplayed.indexOf(message), 1); // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = []; function displayMessage(message) { if (messagesDisplayed.indexOf(message) > -1) { return false; } messagesDisplayed.push(message); // Some magic here to display the message } function removeMessage(message) { messagesDisplayed.splice(messagesDisplayed.indexOf(message), 1); // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = []; function displayMessage(message) { if (messagesDisplayed.indexOf(message) > -1) { return false; } messagesDisplayed.push(message); // Some magic here to display the message } function removeMessage(message) { messagesDisplayed.splice(messagesDisplayed.indexOf(message), 1); // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = []; function displayMessage(message) { // Check if message has already been displayed if (messagesDisplayed.indexOf(message) > -1) { return false; } messagesDisplayed.push(message); // Some magic here to display the message } function removeMessage(message) { // Remove message as displayed messagesDisplayed.splice(messagesDisplayed.indexOf(message), 1); // Some magic here to remove the message }
A new type of object
const mySet = new Set();
Store unique values of any type
mySet.add('test'); mySet.add({ testObject: true });
Similar to an array, but easier to use:
mySet.has('test'); // true mySet.delete('test'); mySet.clear(); mySet.size; mySet.keys(); mySet.values(); mySet.entries(); mySet.forEach();
Stop duplicate messages from displaying
function displayMessage(message) { // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = new Set(); function displayMessage(message) { // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = new Set(); function displayMessage(message) { messagesDisplayed.add(message); // Some magic here to display the message } function removeMessage(message) { // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = new Set(); function displayMessage(message) { messagesDisplayed.add(message); // Some magic here to display the message } function removeMessage(message) { messagesDisplayed.delete(message); // Some magic here to remove the message }
Stop duplicate messages from displaying
const messagesDisplayed = new Set(); function displayMessage(message) { if (messagesDisplayed.has(message)) { return false; } messagesDisplayed.add(message); // Some magic here to display the message } function removeMessage(message) { messagesDisplayed.delete(message) // Some magic here to remove the message }
const messagesDisplayed = new Set(); function displayMessage(message) { if (messagesDisplayed.has(message)) { return false; } messagesDisplayed.add(message); // Some magic here to display the message } function removeMessage(message) { messagesDisplayed.delete(message) // Some magic here to remove the message }
What if hundreds of messages are displayed?
const testMessage = { contents: 'Barry is such a handsome bear' }; displayMessage(testMessage); const importantMessage = { contents: 'Anteaters suck', important: true }; displayMessage(importantMessage); // etc... (hundreds of these)
What if hundreds of messages are displayed?
const messagesDisplayed = new Set(); function displayMessage(message) { if (messagesDisplayed.has(message)) { return false; } messagesDisplayed.add(message); // Some magic here to display the message }
Similar to the Set object
Used for storing objects
Use Weak Sets when storing objects
const weakSet = new WeakSet(); weakSet.add(object); weakSet.has(object); weakSet.delete(object);
const myMap = new Map();
myMap.set('testKey', 'testValue');
May have used objects in ES5
const myMap = { 'testKey': 'testValue' }
Maps are more readable, and easier to use
const myMap = new Map(); myMap.set('testKey', 'testValue'); myMap.has('testKey'); // true myMap.get('testKey'); // 'testValue' myMap.delete('testKey'); myMap.clear(); myMap.size; myMap.keys(); myMap.values(); myMap.entries(); myMap.forEach();
The same concept as Weak Set
const weakMap = new WeakMap(); weakMap.set(object, value); weakMap.has(object); weakMap.get(object); weakMap.delete(object);
The keys are weak, not the value
Iterating arrays in ES5
Lots of choices
None are great
for (var index = 0; index < myArray.length; index++) { var value = myArray[index]; // ... }
myArray.forEach(function(value, index) { // ... });
for (var index in myArray) { var value = myArray[index]; // ... }
Iterating arrays in ES6
One choice
It's great!
for (let value of myArray) { // ... }
for (let index of myArray.keys()) { // ... }
for (let [index, value] of myArray.entries()) { // ... }
Works for any iterable object
For everything else, either;
function displayMessage(content, priority = 1) { console.log(`Priority: $(priority)`); // Some magic here to display the message } displayMessage('Hello, I am a message'); // Priority: 1 displayMessage('Something went wrong!', 10); // Priority: 10
function displayMessage(content, priority = 1, position = { x: 0, y: 0 }, title = null) { // Some magic here to display the message }
displayMessage('Default message'); displayMessage('Centered message', 1, { x: '50%', y: '50%'}); displayMessage('Error message with title', 10, { x: 0, y: 0 }, 'Uh oh');
function displayMessage({ content, priority = 1, position = { x: 0, y: 0 }, title = null }) { // Some magic here to display the message }
displayMessage({ content: 'Default message' }); displayMessage({ content: 'Centered message', position: { x: '50%', y: '50%' } }); displayMessage({ content: 'Error message with title', priority: 10, title: 'Uh oh' });
const defaultMessage = { content: 'Default message' }; const centeredMessage = { content: 'Centered message', position: { x: '50%', y: '50%' } }; const errorMessage = { content: 'Error message with title', priority: 10, title: 'Uh oh' };
Is there a better way to represent a message?
Custom type of object
Has its own functions and properties
class Message { getContent() { return 'Hello, I am a message'; } } const myMessage = new Message(); console.log( myMessage.getContent() ); // Hello, I am a message
All these lines of code to create messages?!
class TestMessage { getContent() { return 'Hello, I am a message'; } } const myTestMessage = new TestMessage(); class ErrorMessage { getContent() { return 'Something went wrong!'; } } const myErrorMessage = new ErrorMessage();
Just one class / template - Message
const myMessage = new Message(); myMessage.setContent('Hello, I am a message'); console.log( myMessage.getContent() ); // Hello, I am a message! const errorMessage = new Message(); errorMessage.setContent('Something went wrong!'); console.log( errorMessage.getContent() ); // Something went wrong!
class Message { setContent(content) { this.content = content; } getContent() { return this.content; } }
const myMessage = new Message(); myMessage.setContent('Hello, I am a test message'); myMessage.content = 'Hello, I am a test message'; console.log( myMessage.getContent() ); console.log( myMessage.content );
class Message { set content(content) { this.content = content; } get content() { return this.content; } }
class Message { set content(content) { this.content = content; } }
This may make it easier to spot
class Message { setContent(content) { this.setContent(content); } }
Maximum call stack size exceeded
class Message { set content(content) { this.content = content; this._content = content; } get content() { return this.content; return this._content; } }
This seems like a lot of work... why bother?
You're right!
A regular object does getters and setters
const myMessage = {}; myMessage.content = 'Hello, I am a test message'; console.log( myMessage.content ); // Hello, I am a test message
class Message { // Empty }
const myMessage = new Message(); myMessage.content = 'Hello, I am a test message'; console.log( myMessage.content ); // Hello, I am a test message myMessage.whatever = 'Anything goes'; console.log( myMessage.whatever ); // Anything goes
Let's get clever with classes
class Message { set priority(priority) { if (priority < 1 || priority > 10) { throw new Error('Priority must be between 1 and 10'); } this._priority = priority; } get priority() { return this._priority; } }
const myMessage = new Message(); myMessage.priority = 20; // ERROR: Priority must be between 1 and 10 myMessage.priority = 5; console.log( myMessage.priority ); // 5
class Message { get log() { return `${this.timestamp} - [${this.priority}] ${this.content}`; } }
const myMessage = new Message(); myMessage.content = 'Hello, this is a test message'; myMessage.priority = 5; myMessage.timestamp = new Date(); console.log( myMessage.log ); // Mon Nov 16 2015 20:24:01 GMT+0000 (GMT) - [5] Hello, this is a test message
Wouldn't it be nice if timestamp was automatically set?
Function that runs when the class is created
class Message { constructor() { this.timestamp = new Date(); this.priority = 5; } }
const myMessage = new Message(); console.log( myMessage.timestamp ); // Mon Nov 16 2015 20:24:01 GMT+0000 (GMT)
Construtors accept parameters too
class Message { constructor(content) { this.content = content; this.timestamp = new Date(); this.priority = 5; } }
const testMessage = new Message('This is a test message');
Allows a class to inherit everything from another class
class DebugMessage extends Message { // Empty } class ErrorMessage extends Message { // Empty }
const myDebugMessage = new DebugMessage('I am a debug message'); const myErrorMessage = new ErrorMessage('I am an error message');
Everything still works!
class Message { setContent(content) { this.content = content; } }
class ErrorMessage extends Message { setContent(content) { content = content + '!!!'; super.setContent(content); } }
const myMessage = new Message('I am a message'); console.log( myMessage.content ); // I am a message const myErrorMessage = new ErrorMessage('I am an error message'); console.log( myErrorMessage.content ); // I am an error message!!!
class Message { set content(content) { this._content = content; } }
class ErrorMessage extends Message { set content(content) { content = content + '!!!'; super.content = content } }
class Message { constructor(content) { this.content = content; this.priority = 5; } }
class DebugMessage extends Message { constructor(content) { super(content); this.priority = 1; } }
const myMessage = new Message('I am a message'); console.log( myMessage.priority ); // 5 const myDebugMessage = new DebugMessage('Just testing'); console.log( myErrorMessage.priority ); // 1
New syntax to easily create anonymous functions
// ES5 const debugMessages = messages.filter(function(message) { return message.priority === 1; });
//ES6 const debugMessages = messages.filter((message) => { return message.priority === 1; });
const debugMessages = messages.filter((message) => { return message.priority === 1; });
const debugMessages = messages.filter((message) => message.priority === 1);
const debugMessages = messages.filter(message => message.priority === 1);
// ES5 anonymous function function(param1, param2) { // ... }
// ES6 arrow function (param1, param2) => { // ... }
They look different... so what?
What's wrong with this code?
class Message { constructor() { this.priority = 5; } increasePriorityEverySecond() { setInterval(function() { this.priority = this.priority + 1; }, 1000); } }
class Message { increasePriorityEverySecond() { var self = this; setInterval(function() { self.priority = self.priority + 1; }, 1000); } }
class Message { increasePriorityEverySecond() { setInterval(function() { this.priority = this.priority + 1; }.bind(this), 1000); } }
Arrow functions retain this from parent scope
class Message { increasePriorityEverySecond() { setInterval(() => { this.priority = this.priority + 1; }, 1000); } }
class Message { increasePriorityEverySecond() { setInterval(() => this.priority = this.priority + 1, 1000); } }
class Message { increasePriority() { this.priority = this.priority + 1 } increasePriorityEverySecond() { setInterval(() => this.increasePriority(), 1000); } }
// Globals var myModule = window.myModule; // AMD / RequireJS define(['myModule'], function(myModule) { }); // CommonJS / npm module.exports = myModule; var myModule = require('myModule'); // ES6 export myModule; import { myModule } from 'myModule';
// Globals var myModule = window.myModule; // AMD / RequireJS define(['myModule'], function(myModule) {}); // CommonJS / npm module.exports = myModule; var myModule = require('myModule'); // ES6 modules export myModule; import { myModule } from 'myModule';
// message.js export class Message { // ... }
// main.js import { Message } from './message'; const myMessage = new Message();
Multiple exports
// message.js export class Message { // ... } export function displayMessage(message) { // ... }
Multiple imports
// main.js import { Message, displayMessage } from './message'; const myMessage = new Message(); displayMessage(myMessage);
// /vendor/message.js export class Message {}
// /app/message.js export class Message {}
Named imports
import { Message as MessageVendor } from '/vendor/message.js'; import { Message as MessageApp } from '/app/message.js';
ES6 does not specify a standard for loading files
ES6 only specifies a standard forthe import & export syntax
import { nope } from '../the/browser/does/not/know/how/to/load/this/file';
Solution
Use a bundling tool that understands ES6 modulestestInternetConnection(function() { fetchMessagesFromServer(function(messages) { messages = JSON.parse(messages); if (messages[0] == null) { displayError('Could not email first message'); logger.log('First message does not exist'); } else { sendEmail( 'test@example.com', 'Here is the first message: ' + messages[0], function() { displayMessage('Success!'); }, function(error) { displayError('Could not email first message'); logger.log('Error sending email: ' + error); } ); } }, function(error) { displayError('Could not email first message'); logger.log('Error fetching messages from server: ' + error); }); }, function(error) { displayError('Could not email first message'); logger.log('Internet connection test failed: ' + error); });
testInternetConnection({ onComplete: function() { fetchMessagesFromServer({ onComplete: function(messages) { messages = JSON.parse(messages); if (messages[0] == null) { displayError('Could not email first message'); logger.log('First message does not exist'); } else { sendEmail({ email: 'test@example.com', message: 'Here is the first message: ' + messages[0], onComplete: function() { displayMessage('Success!'); }, onError: function(error) { displayError('Could not email first message'); logger.log('Error sending email: ' + error); } }); } }, onError: function(error) { displayError('Could not email first message'); logger.log('Error fetching messages from server: ' + error); } }); }, onError: function(error) { displayError('Could not email first message'); logger.log('Internet connection test failed: ' + error); } });
There's a better way...
function fetchMessagesFromServer() { // This function takes time // Return to caller a Promise object straight away // After some time has passed, update the Promise with either; // 1. A value (yay) // 2. An error (aw) return new Promise(function(resolve, reject) { jQuery.get('http://example.com/messages.json') // (time passes) .done(function(result) { // Update the Promise with a value resolve(result); }) .fail(function() { // Update the Promise with an error reject('Could not fetch messages from server'); }) }); }
const messages = fetchMessagesFromServer(); console.log( messages ); // Promise - what do I do with this?!
fetchMessagesFromServer() .then(function(value) { // Promise resolved console.log(value); }) .catch(function(value) { // Promise rejected console.log(value); });
So what?
fetchMessagesFromServer() .then(function(messages) { // ... }) .catch(function(error) { // ... });
fetchMessagesFromServer( function(messages) { // ... }, function(error) { // ... } );
fetchMessagesFromServer({ onComplete: function(messages) { // ... }, onError: function(error) { // ... } });
fetchMessagesFromServer() .done(function(messages) { // ... }) .fail(function(error) { // ... });
The power of Promises:
Chaining them together
The function within then() can do 3 types of things;
1. Return another promise
fetchMessagesFromServer() .then(function(messages) { const promise = sendEmail('test@example.com', messages); return promise; }) .then(function(email) { // ... }) .catch(function(error) { // ... });
The function within then() can do 3 types of things;
2. Return a value
fetchMessagesFromServer() .then(function(messages) { return messages[0]; }) .then(function(firstMessage) { // ... });
This is the same as resolving a Promise
The function within then() can do 3 types of things;
3. Throw an error
fetchMessagesFromServer() .then(function(messages) { // Try to do something a little crazy throw new Error('Uh oh'); }) .catch(function(error) { // ... });
This is the same as rejecting a Promise
testInternetConnection() .then(function() { return fetchMessagesFromServer(); }) .then(function(messages) { return JSON.parse(messages); }) .then(function(messages) { if (messages[0] == null) { throw new Error('First message does not exist'); } else { return messages[0]; } }) .then(function(firstMessage)) { return sendEmail( 'test@example.com', 'Here is the first message: ' + firstMessage ); }) .then(function() { displayMessage('Success!'); }) .catch(function(error) { displayError('Could not email first messsage'); logger.log(error); });
class MessageService { fetchMessagesAsJson() { return testInternetConnection() .then(fetchMessagesFromServer) .then(JSON.parse); } } class MessageEmailer { _getFirstMessage(messages) { if (messages[0] == null) { throw new Error('First message does not exist'); } else { return messages[0]; } } _emailMessage(message) { return sendEmail( this.email, 'Here is the message: ' + message ); } send(email) { MessageService.fetchMessagesAsJson() .then(this._getFirstMessage) .then(this._emailMessage) .then(() => displayMessage('Success!')) .catch((error) => { displayError('Could not email message'); console.log(error); }) } }
Yes, there are more...
Final message...
Do it for Barry