JavaScript that scales
@CitizenOfTheWeb ali.arafati@axis.com
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()
We Bare Bears
function getTallPeopleCount(people) {
var count;
for (var i = 0; i < people.length; i++) {
if (people.height >= 180) {
count++;
}
}
return count;
}
function getTallPeopleCount(people) {
return people.filter(function (person) {
return person.height >= 180;
}).length;
}
function getTallPeopleCount(people) {
return people.filter(person => person.height >= 180).length;
}
ECMAScript 3 - 1999
ECMAScript 5 - 2009
ECMAScript 6 - 2015
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
function getTallPeopleCount(people) {
return people.filter(function(person) {
return person.height >= 180;
}).length;
}
Polyfill (or shim)
ES5 Shim: https://github.com/es-shims/es5-shim
Compatibility table for ES5: http://kangax.github.io/compat-table/es5/
ECMAScript 6:
ES6 Shim: https://github.com/es-shims/es6-shim
1
Syntatic features
function getTallPeopleCount(people) {
return people.filter(person => person.height >= 180).length;
}
function getTallPeopleCount(people) {
return people.filter(function (person) {
return person.height >= 180;
}).length;
}
Transpilers (i.e. compilers)
2
ECMAScript 5 features (getter/setter)
var object = {
get a() { return object._a; }
set a(value) { object._a = value; }
};ECMAScript 6 features (proxies)
var handler = {
get: function(target, name) {
return name in target ?
target[name] :
37;
}
};
var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37IE8 and lower 😞
3
no ES5 support 😞
no ES3 support
ES3
ES5
ES 2015 - ES6
ES 2016
ES 2017
ES 2018
ES 2019
1999
2009
state of the art JavaScript
NodeJS
browsers
legacy
Smart phones
Node 6.5
image source: http://www.lexolutionit.com/article_details.php?id=337
const bad = new String('WAT?');
const areWeGood = !!condition;
const howFar = ~bad.indexOf('?');
const cats = localStorage.getItem(STORAGE_KEY);
const result = eval('2 + 12');
setTimeout('function() { console.log(1) }');
setInterval(() => { checkTheWeather(); }, 1000);// invalid
$rootScope.$on('destroy', function () {
// ...
}); // error: You probably misspelled $on("$destroy").// valid
angular.module('myModule').controller('MyController', function () {
// ... MyCtrl or MyControler or Mycontroller is not valid
});
// invalid
$rootScope.$on('event', function () {
// ...
}); // error: The "$on" call should be assigned to a variable,
// in order to be destroyed during the $destroy eventfunction getTallPeopleCount(people) {
return people.filter(person => persom.height >= 180).length;
}
function getTallPeopleCount(people) {
return people.filter(person => person.hieght >= 180).lenght;
}
function sortPeopleByName(people) {
return people.sort(function(p1, p2) {
p1.name.localCompare(p2.name);
});
}
1
2
function getGamePlayersAsync() {
return Game.waitForConnection(connection => {
return connection.players;
});
}
3
function getGamePlayersAsync() {
return Game.waitForConnection(connection => {
console.log(connection);
return connection.players;
});
}
function getTallPeopleCount(people) {
return people.filter(person => persom.height >= 180).length;
}
1
// person.ts
interface Person {
name: string;
}
function getName(person: Person) {
return person.name;
}// index.ts
let employee = {
name: 'Ali',
company: 'Axis'
};
getName(employee); // OK
getName({ name: 'Donald Duck' }); // OK
getName({ name: 13 }); // ERROR
getName({ blah: 'yada' }); // ERROR
let movie = getMovieByName('Fantastic Beasts and Where to Find Them');
getName(movie); // OK
With TypeScript 2.0, the type checker analyses all possible flows of control in statements and expressions to produce the most specific type possible (the narrowed type) at any given location for a local variable or parameter that is declared to have a union type.
function normalize(id: string | number) {
if (typeof id === 'number') {
return id;
}
return +id.split(':')[0];
}
normalize(1234);
normalize('1234:xp:12')let k;
k = '';
k.substr(1);
k = 12.34;
k.toFixed();function f1() {
let x = [];
x.push(5);
if (cond()) {
x[1] = "hello";
}
x.unshift(true);
return x;
}string
"foo"
""
undefined
null
number
42
-3.1415
undefined
null
NaN
boolean
true
false
undefined
null
string
"foo"
""
undefined
null
number
42
-3.1415
NaN
boolean
true
false
var r = validateSomething();
if (r.success === true) {
r.data;
} else {
r.errorNo;
r.message;
}type Result<T> =
{ success: true; data: T } |
{ success: false; errorNo: number; message: string; };
function validateSomething(): Result<number>;const KEY = '[SECURE KEY]';
const PI = 3.14;
const flag = true;
const x = cond() ? PI : '3.1415';
let bit: 1 | 0;
let env: 'production' | 'development';
interface UserRoot {
type: 'UNIL' | 'LDAP';
}
interface LDAP extends UserRoot {
type: 'LDAP';
username: string;
name: string;
groups: string[];
roles: string;
}
interface UNIL extends UserRoot {
type: 'UNIL';
username: string | number;
hid: string;
}
type User = LDAP | UNIL;interface User {
type: 'UNIL' | 'LDAP'; // or string
username: string | number;
hid?: string; // note the tentetive type
name?: string;
groups?: string[];
roles?: string;
}
document.querySelector('script').src; // OK
document.querySelector('a').href; // OKinterface NodeSelector {
querySelector(selectors: 'a'): ElementAnchorElement | null;
querySelector(selectors: 'script'): ElementScriptElement | null;
querySelector(selectors: string): Element | null;
// ...
}interface AddToCartAction {
type: 'ADD_TO_CART';
product: {
id: number;
name: string;
};
}
interface RemoveFromCartAction {
type: 'REMOVE_FROM_CART';
productId: number;
}
type ShoppingCartActions = AddToCartAction | RemoveFromCartAction;export default (state = initialState, action: ShoppingCartActions) => {
switch (action.type) {
case ADD_TO_CART:
// action is AddToCartAction
break;
case REMOVE_FROM_CART:
// action is RemoveFromCartAction
break;
default: return state;
}
}
function getUser() {
return request('user').then(user => {
if (user.hasProjects) {
return request('projects').then(projects => {
return {
...user,
projects
};
});
}
return user;
});
}getUser().then(user => {
if (user.hasProjects) {
user.projects; // OK
} else {
user.projects; // Empty, or maybe undefined
}
});function getUser() {
return request('user').then(user => {
if (user.hasProjects) {
return request('projects').then(projects => {
return {
...user,
projects
};
});
}
return user;
});
}async function getUser() {
let user = await request('user');
if (user.hasProjects) {
user.projects = await request('projects');
}
return user;
}import { deprecate } from 'core-decorators';
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
facepalmHarder() {}
}
import { throttle } from 'core-decorators';
class Editor {
content = '';
@throttle(500, { leading: false })
updateContent(content) {
this.content = content;
}
}function throttle(timeout, config) {
return function(target, methodName) {
const originalMethod = target[methodName];
let h;
target.defineProperty(target, methodName, {
value: function(...args) {
if (h) { clearTimeout(h); }
h = setTimeout(() => {
originalMethod.apply(this, args);
}, timeout);
}
});
}
}class Post {
public id: number;
public title: string;
public content: string;
@JSON('writters.author')
public author: string;
@JSON('writters.editor')
public editor: string;
}// Post json structure
{
id: 1,
title: 'foo',
content: 'bar',
writters: {
author: 'buzz',
editor: 'bizz'
}
}var Post = (function () {
function Post() {
}
return Post;
}());
__decorate([
JSON('writters.author'),
__metadata("design:type", String)
], Post.prototype, "author");
__decorate([
JSON('writters.editor'),
__metadata("design:type", String)
], Post.prototype, "editor");emitDecoratorMetadata
@API({
endpoint: '/posts/:id',
canUpdate: (user, post) => user.canPost(post),
canDelete: (user, post) => user.canDelete(post)
})
class Post {
public id: number;
public title: string;
public content: string;
@JSON('writters.author')
public author: string;
@JSON('writters.editor')
public editor: string;
@API({
endpoint: (post) => `/posts/${post.id}/comments`
})
@JSON('user_comments')
public comments: Comment[];
}@CitizenOfTheWeb https://slides.com/aliai/typescript