Presented by:
JavaScript that doesn't suck :)
var
is dead.let
& const
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);
this
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 Element
s 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();
Set
s and Map
s
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;
WeakMap
s and WeakSet
s
const obj = {
// ...
}
const wm = new WeakMap();
wm.set(obj, 42); // store some metadata about obj.
WeakMap
must be objectsWeakMap
s do not hold a strong reference to their keys.Promise
s
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?
async...await
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.
.bind
to any html or custom attribute..one-way
, .two-way
, .one-time
.
<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.
<router-view>
element.configureRouter()
method in view model.
// 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.
activate()
method called with url and query parameters.deactivate()
is called.canActivate()
and canDeactivate() to control navigation.
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.
inject
decorator.
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()