Welcome. Traceur project's goal is to evolve JavaScript to make writing web applications more fun.
Use the arrow keys to advance slides. Press N to toggle speaker notes. If the notes don't fit on screen, zoom out with Cmd/Ctrl - (minus).
(We took a few implementation shortcuts to build this presentation quickly, so it only works in Google Chrome at the moment.)
Web is open and empowering. People have built amazing Web Apps.
You have to be a gluton for punishment.
Its not just a programmer hacking a web site. Its 50 strong team engineering gmail frontend.
Creative in attempts to build large apps. Notably GWT.
We need to improve the web platform to keep the web vibrant.
Adding features to JS that enables large software development.
Differentiating factors between large applications and small is the need for components.
Reusable pieces of software with well defined boundaries.
Classes are small units of reusable software.
We're going to look at just one aspect of classes - inheritance.
function Actor() { ... } Actor.prototype.moveTo = function (pos) { ... };
function PacMan() { } PacMan.prototype = new Actor(); PacMan.prototype.eat = function() { ... };
var pac = new PacMan(); assertTrue(pac instanceof Actor);
JS has prototypal inheritance - let's take a look.
This is a class, or more specifically a constructor function. The word "function" doesn’t mean what you think it means… unless you know that this is being written in a particular style.
This is how you do inheritance. Set the prototype of the derived to be an instance of the base.
Which allows you to create instances of the derived which are also instances of the base. Works great... ... Except when your base constructor has side effects!
So let's look at what really happens in practice ...
function Actor() { ... } Actor.prototype.moveTo = function (pos) { ... }; function PacMan() { }
PacMan.prototype = (function() { function tempCtor() {}; tempCtor.prototype = Actor.prototype; var proto = new tempCtor(); proto.constructor = PacMan; return proto; })(); PacMan.prototype.eat = function() { ... };
So let's look at how prototypal inheritance is really done.
Now that says - inheritance to me.
Just to be clear. I am not making this up - this is actual best practice.
goog.inherits(Actor, PacMan);
PacMan = Class.create(Actor, ... );
PacMan = Actor.extend(...);
So the library authors decided to fix the problem once and for all.
Once for closure ...
Once for prototype ...
... and Once for base2. All great solutions. None of them interoperable.
class Actor { new() { ... } moveTo: function (pos) { ... } } class PacMan : Actor { new() { ... } eat: function() { ... } }
Say what you mean... Note that you can see that it’s actually a class just by looking at it. This looks easy, but its actually really, really hard. Good designs look easy. There's a lot more to classes than just inheritance.
Another part of reusable components is code distribution.
goog.provide('goog.ui.tree.TreeControl'); goog.require('goog.events.EventType'); goog.require('goog.events.FocusHandler'); goog.require('goog.events.KeyHandler'); goog.require('goog.ui.tree.BaseNode'); goog.require('goog.ui.tree.TreeNode'); goog.require('goog.ui.tree.TypeAhead'); goog.ui.tree.TreeControl = ...;
define("dijit/Tree", ["dojo", "dijit", "text!dijit/templates/TreeNode.html", "text!dijit/templates/Tree.html", "dijit/_Widget", "dijit/_Container", "dijit/_Contained"], function(dojo, dijit) { dojo.declare("dijit.Tree", ... ); return dijit.Tree; });
// goog/ui/tree/TreeControl.js module Events = require('goog/events'); import Events.{EventType, FocusHandler, KeyHandler}; module Tree = require('goog/ui/tree'); import Tree.{BaseNode, TreeNode, TypeAhead}; export TreeControl = ...;
Files are modules when they’re included as modules, and they can contain many exports. The sync/async tension in requires is handled by the language runtime and doesn’t necessarily need extra magic or a compiler to make it perform adequately in most cases.
The syntax for this isn’t final, and we’re working inside of TC39 to improve it, but we like the semantics of this new system.
Object.extend(Array.prototype, { clear: clear, compact: compact, flatten: flatten, reverse: reverse, uniq: uniq, intersect: intersect, clone: clone });
var dots = allDots.clone();
var dots = goog.array.clone(allDots);
Monkey patching is used extensively in real libraries. This example comes from the prototype library. Note they monkey patch by using a monkey patch.
This is handy because you can add helpers that can be called quite naturally.
But monkey patching in large applications leads to subtle conflicts between libraries. Other libraries go to great pains to not monkey patch. This avoids conflicts between libraries, but has a singificant cost to the usability of the API.
module Prototype { export extension Extensions = Array.prototype { clone: function () { ... } } ... [a, b, c].clone() ... }
assertUndefined([].clone);
import Prototype.Extensions; var dots = allDots.clone();
Extensions are defined as part of a module. And are available inside that module.
Outside of their defining module, extensions are not visible.
But they can be imported across module boundaries.
function animate(element, callback) { var left = -1; function next() { left++; if (left < 350) { element.style.marginLeft = left + 'px'; window.setTimeout(next, 5); } else { callback(); } } next(); } animate(document.getElementById('now'), function() { alert('done!'); });
JS is embedded in an environment where blocking calls are bad. So all long running APIs use callbacks. Examples include setTimeout and XHRs.
The callback infects your API in a non-composable way.
function deferTimeout(ms) { var deferred = new Deferred(); window.setTimeout(function() { deferred.callback(); }, ms); return deferred; } function animate(element) { var left = -1, deferred = new Deferred(); function next() { if (++left < 350) { element.style.marginLeft = left + 'px'; deferTimeout(5).then(next); } else { deferred.callback(); } } next(); return deferred; } animate(document.getElementById('now')).then(function() { alert('done!'); });
Library authors invented the Deferred pattern to alleviate the composability issue. It moves the callback from the argument list to the return value. Since the return value is first class it enables composability.
The implementation is still twisted into CPS however.
function deferTimeout(ms) { var deferred = new Deferred(); window.setTimeout(function() { deferred.callback(); }, ms); return deferred; } function animate(element) { for (var left = 0; left < 350; left++) { element.style.marginLeft = left + 'px'; await deferTimeout(5); } } animate(document.getElementById('now')).then(function() { alert('Traceur!'); });
Deferred functions allow writing callback heavy code in a natural linear style. Let the compiler do the CPS transform for you.
It takes some getting used to.
classes and traits | modules |
scoped monkey patching | iterators/generators |
async/callbacks | DOM constructors |
lightweight function syntax | proper tailcalls |
type system | block scoped binding |
proxies | weak maps |
destructuring assignment | rest parameters |
spread operator | default parameter values |
binary data/typed arrays | standard library |