Javascript Rocks
Introduction
Core Javascript Features
Plan
Classes, Modules
Hook
Memoization
API
Javascript Object Notation (JSON)
var point = {x: 51, y: 42};
console.log(point.x, point.y);
- Short notation.
- Really useful for small structures.
- Next: Make a constructor.
Constructor
function Point(x, y) {
return {
x: x,
y: y
};
}
var point = Point(51, 42);
console.log(point);
- Verbose?
- Debugging!
- Next: Add print method.
Constructor with Method attempt
function Point(x, y) {
return {
x: x,
y: y,
print: function () {
console.log(?????.x, ?????.y);
}
};
}
- Functions are first class: they can be used inside an object.
- Problem: Cannot self reference.
- Solution: Make a temporary variable.
Constructor with Method
function Point(x, y) {
var point = {};
point.x = x;
point.y = y;
point.print = function () {
console.log(point.x, point.y);
};
return point;
}
var point = Point(51, 42);
point.print();
- Objects are mutable. You can add attributes after it has been created.
- Objective Complete: We have class with only Objects and Functions.
- Next: Less verbose using
new
operator.
new
Operator
function Point(x, y) {
this.x = x;
this.y = y;
this.print = function () {
console.log(this.x, this.y);
};
}
var point = new Point(51, 42);
point.print();
new
magically creates an empty object this
and returns it at the end of the function.
- Next: Private variable.
Private variable
function Point(x, y) {
this.x = x;
var y_ = y;
this.print = function () {
console.log(this.x, y_);
};
}
var point = new Point(51, 42);
point.print();
- We use a local variable instead of an object property.
- As long as the
print
function lives, the variable y_
exists thanks to the Closure.
- Next: Prototypal Inheritance.
Prototype
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
print: function () {
console.log(this.x, this.y);
}
};
var point = new Point(51, 42);
point.print();
new
also binds __proto__
attribute.
- Functions are Objects. You can add them attributes.
- Next: Memory representation.
Without Prototype
point
x |
y |
print |
move |
intersect |
... |
51 |
42 |
function |
function |
function |
... |
x |
y |
print |
move |
intersect |
... |
51 |
42 |
function |
function |
function |
... |
x |
y |
print |
move |
intersect |
... |
51 |
42 |
function |
function |
function |
... |
x |
y |
print |
move |
intersect |
... |
51 |
42 |
function |
function |
function |
... |
x |
y |
print |
move |
intersect |
... |
51 |
42 |
function |
function |
function |
... |
- Disadvantage: Memory expensive
- Disadvantage: Creation expensive: have to fill all the values
- Next: Prototypal approach
With Prototype
point
x |
y |
__proto__ |
51 |
42 |
Point.prototype |
x |
y |
__proto__ |
51 |
42 |
Point.prototype |
x |
y |
__proto__ |
51 |
42 |
Point.prototype |
x |
y |
__proto__ |
51 |
42 |
Point.prototype |
x |
y |
__proto__ |
51 |
42 |
Point.prototype |
| |
Point.prototype
print |
move |
intersect |
... |
__proto__ |
function |
function |
function |
... |
null |
|
- Disadvantage: Indirection on function call
- Advantage: Shared properties possible
- Next: Prototypal approach
Pimp My Class with Helpers
Prototypal inheritance: Object.create
var Point = {
x: 51,
y: 42,
print: function () {
console.log(this.x, this.y);
}
};
var point = Object.create(Point);
point.print();
- Object.create(proto): Creates a new object setting the prototype.
- Next: Point3D?
Multi-Level Inheritance
var Point = {
x: 51,
y: 42,
print: function () {
console.log(this.x, this.y);
}
};
var Point3D = Object.create(Point);
Point3D.z = 666;
Point3D.print = function () {
console.log(this.x, this.y, this.z);
}
var point = Object.create(Point3D);
point.print();
- Object.create can also be used for multi-level inheritance.
- Next: Unified syntax.
Object.extend
var Point = {
x: 51,
y: 42,
print: function () {
console.log(this.x, this.y);
}
};
var Point3D = Object.extend(Object.create(Point), {
z: 666,
print: function () {
console.log(this.x, this.y, this.z);
}
});
var point = Object.create(Point3D);
point.print();
- Object.extend(base, ext): Copies all the
ext
attributes to base
.
- Next: Class Framework
Class Framework: JS.Class
var Point = new JS.Class({
initialize: function(x, y) {
this.x = x;
this.y = y;
},
print: function() {
console.log(this.x, this.y);
}
});
var Point3D = new JS.Class(Point, {
initialize: function(x, y, z) {
this.callSuper(x, y);
this.z = z;
},
print: function() {
console.log(this.x, this.y, this.z);
}
});
var point = new Point3D(42, 51, 666);
point.print();
Module,
Namespace?
Namespace
lib3d.jsvar Lib3D = { /* ... */ };
lib3d.particle.jsLib3D.Particle = { /* ... */ };
lib3d.collada.jsObject.extend(Lib3D, {
ColladaLoader: { /* ... */ }
});
app.html<script src="lib3d.js"></script>
<script src="lib3d.collada.js"></script>
<script>Lib3D.ColladaLoader.load(...);</script>
- Load only modules we need: Keep size small.
- Extension can be written by someone outside the Lib3D project.
- Next: What if Lib3D already exists?
Local Namespace
lib3d.js{
/* ... */
}
app.jsvar Lib3D = require('lib3d.js');
- Use local variables in order to avoid name-clash.
function require(filename) {
var file = download(filename);
return eval(file);
}
- Eval is required to process code at runtime.
- Next: I don't want to freeze the browser, Asynchronous maybe?
Local Namespace: Asynchronous
app.jsrequire('lib3d.js', function (Lib3D) {
Lib3D.ColladaLoader.load();
});
- Use function argument name to avoid name-clash.
function require(filename, callback) {
downloadAsync(filename, function (file) {
callback(eval(file));
});
}
- Continuation Passing Style: Heavy use of callbacks/continuation.
LD_PRELOAD?
Situation
function ProcessChatMessage(message, source, channel) {
// ... Game Code ...
}
- Game in the browser.
- For your damage charting extension, you want to watch the combat log.
- Next: One solution.
Ugly Hack: Game Patching
function ProcessChatMessage(message, source, channel) {
if (channel == COMBAT_LOG_CHANNEL) {
// ... Extension Code ...
}
// ... Game Code ...
}
- Edit the game files.
- Next: Hook technique.
Hook Technique
var defaultProcessChatMessage = ProcessChatMessage;
ProcessChatMessage = function(message, source, channel) {
if (channel == COMBAT_LOG_CHANNEL) {
// ... Extension Code ...
}
defaultProcessChatMessage(message, source, channel);
};
- Powerful: You can alter arguments, call the function multiple times.
- Target function does not need to be written in a special form.
- Decentralized: Multiple hooks can be added independently.
- No library required: Uses only basic Javascript.
Memoization
...What!?
Situation
- Twitter Application
- When you mouseover a username, a tooltip is displayed with more infos.
username.mouseover(function () {
var user = getUser(username.id);
displayTooltip(user);
});
function getUser(id) {
// Fetch Request (Synchronous only for the example!)
return user;
}
- Problem: If you mouseover twice the same user, there will be two requests!
- Solution: Add a cache.
Object as Cache
var cache = {};
function getUser(id) {
if (id in cache) {
return cache[id];
}
// Fetch Request
return cache[id] = user;
}
Self-invoking Function
var getUser = (function () {
var cache = {};
return function (id) {
if (id in cache) {
return cache[id];
}
// Fetch Request
return cache[id] = user;
};
})();
Non-obstrusive
function memoize (f) {
var cache = {};
return function () {
if (arguments in cache*) {
return cache[arguments];
}
return cache[arguments] = f.apply(this, arguments);
};
}
var getUser = memoize(function (id) {
// Fetch Request
return user;
});
- * This code is simplified (
arguments
is not a real array, Elements toString
must be unique and not contain any comma ,
)
Augment Function Prototype
Function.prototype.memoize = function () {
var cache = {};
var f = this;
return function () {
if (arguments in cache) {
return cache[arguments];
}
return cache[arguments] = f.apply(this, arguments);
};
}
function getUser (id) {
// Fetch Request
return user;
};
getUser = getUser.memoize();
- Every function have Function.prototype as prototype.
Write Less, Do More
API Matters
Default Options
function fooo(options) {
options = Object.extend({
validate: false,
limit: 10
}, options);
// Do something with the options
}
fooo();
fooo({validate: true});
fooo({validate: true, limit: 20});
Method Chaining
Example
$('<input>')
.addClass('username')
.val('Username')
.click(function () { /* ... */ })
.appendTo(login_form);
Implementation
function Fooo() {}
Fooo.prototype = {
method: function () {
// Do something
return this;
}
};
var obj = new Fooo();
obj.method().method();
Method Chaining Examples
HTML Document Traversing$('li') // li: List Item
.has('ul') // ul: Unordered List
.eq(1)
.parent()
SQL QuerySQL.Select()
.field('*')
.table('users')
.limit(10)
Asynchronous Tasks$('<div/>')
.fetch('navigation.html') // Asynchronous
.addClass('column')
.appendTo('#side')
Polymorphism
Augment HTML Element$(username)
Augment Multiple HTML Elements$([username, password])
Create HTML Fragment$('<div/>')
CSS Query for HTML Element$('input[type=text].username')
- jQuery manipulates one or more HTML elements
- One constructor
Expected Features
Set properties$('<input/>', {type: 'text', class: 'user'})
Make the search relative$('input[type=text].username', login_form)
Execute function after page load$(function () { /* ... */ });
- Add useful and expected features as optional arguments
key('shift + a', '⌘ + alt + 1', function (event, handler) {
console.log(handler.shortcut); // 'shift + a'
console.log(handler.keys); // {shift: true, a: true}
});
- Event Driven Programming: Lambda functions makes it easy to write.
- Strings are good for introspection.
- Variadic arguments, continuation as the last argument.
Variables
var color = '#4D926F'
Style({
'#header': {
color: color
},
h2: {
color: color
}
})
|
#header {
color: #4D926F;
}
h2 {
color: #4D926F;
}
|
Functions
function roundedCorners(radius) {
return {
'border-radius': radius,
'-webkit-border-radius': radius,
'-moz-border-radius': radius
}
}
Style({
'#header': Style(roundedCorners('15px'), {
color: 'red'
})
})
|
#header {
border-radius: 15px;
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
color: red;
}
|
Pattern Matching with wu.match
Caml
let rec map f list =
match list with
[] -> []
| head::tail -> f(head) :: map f tail
Javascript
function map(f, list) {
return match(list,
[ [] ] , [],
[ Array ], function (arr) {
var head = arr[0], tail = arr.splice(1);
return [f(head)].concat(map(f, tail));
}
);
}
map(function (n) { return n * n; }, [1, 2, 3, 4])
>> [1, 4, 9, 16]
Pattern Matching with wu.match
CoffeeScript
map = (f, list) ->
match list,
[ [] ] , [],
[ Array ], (head, tail...) -> [f head].concat map f, tail
Caml
let rec map f list =
match list with
[] -> []
| head::tail -> f(head) :: map f tail
Javascript People
Myself!
Famous Javascript Gurus
←
→
/
#