Presented by:
JavaScript that doesn't suck :)
var pi = 3.141592653; // is now const pi = 3.141592653;
// this works. stop the insanity. for (var i=0; i<10; i++) { console.log(i); } console.log(i);
// this does not work :) for (let i=0; i<10; i++) { console.log(i); } console.log(i);
[1,2,3].map(a => a+1);
function() { var self = this; self.name = 'Sean'; setInterval(function() { console.log(self.name); // ugly :( }); }
function() { this.name = 'Sean'; setInterval(() => { console.log(this.name); // => shares the same this // with the surrounding code! }); }
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } getFullName() { return this.firstName + ' ' + this.lastName; } }
class Developer extends Person { // static method called with Developer.curse(); static curse() { return 'thou shalt forever be off by one...'; } constructor(firstName, lastName, isRemote) { super(firstName, lastName); this._isRemote = isRemote; } // getter, used via developerInstance.isRemote get isRemote() { return this._isRemote; } // setter, used via developerInstance.isRemote = false set isRemote(newIsRemote) { throw new Error('Cannot re-assign isRemote!'); } }
@isTestable(true) class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } @readonly getFullName() { return this.firstName + ' ' + this.lastName; } }
Decorators are annotations which allow you to define cross-cutting modifications to classes and methods.
Decorators are executed at runtime.
Built-in classes like Array, Date and DOM Elements can be subclassed!
Making module syntax a native part of the language!
// lib/math.js export function sum(x, y) { return x + y; } export var pi = 3.141593;
// app.js import * as math from "lib/math"; alert("2π = " + math.sum(math.pi, math.pi));
// otherApp.js import {sum, pi} from "lib/math"; alert("2π = " + sum(pi, pi));
/* before, in Person, we had this: */ getFullName() { return this.firstName + ' ' + this.lastName; }
/* now we can do this!*/ getFullName() { return `${this.firstName} ${this.lastName}`; }
let a = ['a','b','c'];
for (let i in a) { console.log(i); } // prints 0 1 2 (which is pretty useless)
for (let i of a) { console.log(prop); } // prints a b c :)
You can use the Iterator protocol in your own functions and classes to make anything iterable via for...of
function f(x, y=12) { // y is 12 if not passed (or passed as undefined) return x + y; } f(3) == 15
function f(x, ...y) { // y is an Array return x * y.length; } f(3, "hello", true) == 6
function f(x, y, z) { return x + y + z; } // Pass each elem of array as argument f(...[1,2,3]) == 6
let a, b, rest; [a, b] = [1, 2] {a, b} = {a:1, b:2} // a === 1, b === 2 [a, b, ...rest] = [1, 2, 3, 4, 5] // a === 1, b === 2, rest === [3,4,5]
function f() { return [1,2]; } [a, b] = f();
const s = new Set(); s.add("hello").add("goodbye").add("hello"); s.size === 2; s.has("hello") === true;
const m = new Map(); m.set("hello", 42); m.set("goodbye", 34); m.get("goodbye") == 34;
const obj = { // ... }
const wm = new WeakMap(); wm.set(obj, 42); // store some metadata about obj.
const p = new Promise((resolve, reject) => { setTimeout(() => { Math.random() < 0.5 ? resolve() : reject(); }, 500); }); p.then(() => { console.log('Resolved!'); }) .catch(() => { console.log('Rejected!'); });
function *getTime() { while(true) { yield Date.now(); } } const timer = getTime(); console.log(timer.next()); // { value: 1454906307698, done: false } console.log(timer.next()); // { value: 1454906307710, done: false } console.log(timer.next()); // { value: 1454906307711, done: false }
You can also use the for...of loop with Generators :)
const summer = (function *sum() { let sum = 0; while(true) { sum += yield sum; } })(); summer.next(); // start summer by making it yield once // now we can pump values into it, and receive the current sum console.log(summer.next(1)); // { value: 1, done: false } console.log(summer.next(2)); // { value: 3, done: false } console.log(summer.next(3)); // { value: 6, done: false }
Calling next() on a generator makes it pause execution. When the generator is restarted by another call to next(), the argument passed to next() replaces the yield expression.
Let's say we have some asynchronous function returning a Promise:
function longRunning(done) { return new Promise((resolve) => { setTimeout(() => { resolve(Math.random()); }, 500); }); }
Normally, we'd use it like this:
longRunning.then((result) => { console.log(result); })
But now we can do something like this...
const script = function *() { let s = yield longRunning(); console.log(s); }();
With the assistance of this horrifying statement:
script.next().value.then((r) => { script.next(r); });
Treating async code like it's synchronous is awesome!
const script = function *() { let s = yield longRunning(); // so cool! console.log(s); }();
So how do we avoid the horror?
const script = function *() { let s = yield longRunning(); console.log(s); }(); script.next().value.then((r) => { script.next(r); });
becomes...
(async function script() { let s = await longRunning(); // even cooler! console.log(s); })(); // no ugliness!
Or, more realistically...
(async function script() { try { let s = await longRunning(); // sequential async let t = await anotherLongRunning(); console.log(s + t); } catch (err) { console.error(err); } })();
Notice that good old-fashioned try-catch blocks work again!
Or, for parallel async
(async function script() { try { let [s,t] = await Promise.all( longRunning(), anotherLongRunning() ); console.log(s + t); } catch (err) { console.error(err); } })();
Almost all of this is available in Node.js natively, right now!
If you're not using it...start. My eyes will thank you.
For more information, the Babel docs are a good reference
As is the Mozilla Developer Network JavaScript reference
// View model: app.js export class App { constructor() { this.heading = 'Hello Aurelia!'; this.firstName = 'John'; this.lastName = 'Doe'; } get fullName() { return `${this.firstName} ${this.lastName}`; } sayWelcome() { alert(`Welcome, ${this.fullName}!`); } }
<!-- View: app.html --> <template> <h2>${heading}</h2> <form> <input type="text" value.bind="firstName"> <input type="text" value.bind="lastName"> <p>${fullName}</p> <button click.trigger="sayWelcome()"> Submit </button> </form> </template>
Minimal framework intrusion in code.
<input type="text" value.bind="user.name"> <div class.bind="row.isActive ? 'active' : ''"> some content </div> <div show.bind="hasError" class="error"> ${err.message} </div> <my-datepicker data.two-way="user.dob"> </my-datepicker>
Adaptive Binding picks optimal observation strategy & minimizes dirty checking.
<!-- View: anywhere.html --> <template> <router-view></router-view> </template>
// View Model: anywhere.js export class Anywhere { configureRouter(config, router) { config.map([ {route: ['', 'welcome'], moduleId: 'welcome'}, {route: 'users', moduleId: 'users'}, {route: 'users/:id', moduleId: 'userdetail'} ]); } }
Any view can be a routing container and they can be nested.
export class UserDetail { constructor() { ... } activate(params) { // assume this.userService is available return this.userService.getUser(params.id) .then(user => this.user = user) .catch(err => ... error handling) } deactivate() { // cleanup } canActivate() { ... } canDeactivate() { ... } }
All lifecycle methods can optionally be implemented.
import {UserService} from 'path/to/user-service'; import {inject} from 'aurelia-framework'; @inject(UserService) export class UserDetail { constructor(userService) { this.userService = userService; } activate(params) { return this.userService.getUser(params.id) .then(...) } }
Constructor based DI makes it easy to mock out dependencies in unit tests.
// View Model: contact-card.js import {bindable, inject} from 'aurelia-framework'; @inject(Element) export class ContactCard { @bindable name; constructor(element) { this.element = element; } attached() { // DOM manipulation } detached() { // cleanup } }
<!-- View: contact-card.html --> <template> <div class="contact-card"> <input type="text" value.bind="name"> ... </div> </template>
<!-- Usage: some-view.html --> <template> <require from="contact-card"></require> <contact-card name.two-way="user.name"> <contact-card> ... </template>
Component lifecycle methods:
created(view), bind(bindingContext), attached(), detached(), unbind()