JavaScript Patterns Stoyan Stefanov, Yahoo! @stoyanstefanov Ajax Experience, Boston 2009 3:25 p.m. Sept 15, 2009
Sep 08, 2014
JavaScript Patterns Stoyan Stefanov, Yahoo! @stoyanstefanov
Ajax Experience, Boston 2009 3:25 p.m. Sept 15, 2009
About me • Yahoo! Search • Yahoo! Exceptional Performance • YSlow 2.0 architect • http://smush.it • Books, articles • http://phpied.com
Types of patterns
• OO Design Patterns (gang of four) • JavaScript-specific coding patterns • Anti-patterns
In this session…
• Object creation patterns • Code reuse patterns • Functional patterns • More on object creation • Design patterns
Object creation patterns
#
Two ways to create objects
• Object literal • Constructor functions
{} vs. new
Object literal
var adam = {}; // clean slate
adam.name = “Adam”; adam.say = function() { return “I am ” + this.name; };
Object literal var adam = { name: “Adam”, say: function() { return “I am ” + this.name; } };
Using a constructor
var adam = new Person(“Adam”); adam.say(); // “I am Adam”
Constructor functions var Person = function(name) {
this.name = name; this.say = function() { return “I am ” + this.name; };
};
Constructor functions var Person = function(name) { // var this = {}; this.name = name; this.say = function() { return “I am ” + this.name; }; // return this; };
Naming convention
MyConstructor myFunction
Enforcing new function Person() { var that = (this === window) ? {} : this;
that.name = name;
that.say = function() { return “I am ” + that.name;
};
return that; }
Enforcing new
this instanceof Person
this instanceof arguments.callee
ES5 FTW
Prototype
var Person = function(name) {
this.name = name;
};
Person.prototype.say = function() {
return “I am ” + this.name;
};
How is the prototype used?
>>> var adam = new Person('Adam'); >>> adam.name; "Adam" >>> adam.say(); "I am Adam"
If you want to reuse it, add it to the prototype
Code reuse patterns
Inheritance – friend or foe
“Prefer object composition to class inheritance” Gang of 4
Borrowing methods pattern
• call() and apply()
notmyobj.doStuff.call(myobj, param1, p2, p3) notmyobj.doStuff.apply(myobj, [param1, p2, p3])
Example: borrowing from Array function f() { var args = [].slice.call(arguments, 1, 3); return args; }
>>> f(1, 2, 3, 4, 5, 6) 2,3
[].slice.call can also be Array.prototype.slice.call
Inheritance by copying properties
function extend(parent, child) { var i, child = child || {}; for (i in parent) { child[i] = parent[i]; } return child; }
Mixins function mixInThese() { var arg, prop, child = {}; for (arg = 0; arg < arguments.length; arg++) { for (prop in arguments[arg]) { child[prop] = arguments[arg][prop]; } } return child; }
var cake = mixInThese( {eggs: 2, large: true}, {butter: 1, salted: true}, {flour: “3 cups”}, {sugar: “sure!”} );
Classical inheritance
function Parent(){ this.name = 'Adam'; } Parent.prototype.say = function(){ return this.name; };
function Child(){}
inherit(Child, Parent);
Option 1
function inherit(C, P) { C.prototype = new P(); }
ECMA standard
Option 2 – rent-a-constructor
function C(a, c, b, d) { P.call(this, arguments); } • Advantage – passes arguments when creating an object • Drawback – won’t inherit from the prototype
Option 3 – rent + prototype
function C(a, c, b, d) { P.call(this, arguments); } C.prototype = new P();
Option 4
function inherit(C, P) { C.prototype = P.prototype; } • Advantage: everybody shares the same prototype • Drawback: everybody shares the same prototype
Option 5
function inherit(C, P) { var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); } • Only inherits properties/methods of the prototype
Option 5 + super
function inherit(C, P) {
var F = function(){};
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
}
Option 5 + super + constructor
reset function inherit(C, P) { var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; }
Prototypal inheritance
• by Douglas Crockford • no class-like constructors • objects inherit from objects • via the prototype
Prototypal inheritance
function object(o) { function F(){} F.prototype = o; return new F(); } ES5
FTW
Prototypal inheritance
>>> var parent = {a: 1};
>>> var child = object(parent);
>>> child.a;
1
>>> child.hasOwnProperty(“a”);
false
Functions
Functions are objects
Self-executable functions
(function(){ var a = 1; var b = 2; alert(a + b); })();
Self-executable functions
(function(a, b){ var c = a + b; alert(c); })(1, 2);
Callbacks
function test(a, b, fn) { fn(a, b); }
test(1, 2, myFunc);
test(1, 2, function(one, two){ console.log(arguments); });
Callback pattern example
document.addEventListener( 'click', animateAndWowUser, false
);
Callback pattern example
var thePlotThickens = function(){ console.log('500ms later...'); };
setTimeout(thePlotThickens, 500);
// anti‐pattern setTimeout("thePlotThickens()", 500);
Returning functions function setup() { alert(1); return function() { alert(2); }; } var my = setup(); // alerts 1 my(); // alerts 2
Returning functions function setup() { var count = 0; return function() { return ++count; }; } var next = setup(); next(); // 1 next(); // 2
Self-overwriting functions function next() { var count = 1; next = function() { return ++count; }; return count; } next(); // 1 next(); // 2
Lazy function definition function lazy() { var result = 2 + 2; lazy = function() { return result; }; return lazy(); } lazy(); // 4 lazy(); // 4
Function properties function myFunc(param){ if (!myFunc.cache) { myFunc.cache = {}; } if (!myFunc.cache[param]) { var result = {}; // … myFunc.cache[param] = result; } return myFunc.cache[param]; }
Init-time branching // BEFORE var addListener = function(el, type, fn) {
// w3c if (typeof window.addEventListener === 'function') { el.addEventListener(type, fn, false);
// IE } else if (typeof document.attachEvent === 'function') { el.attachEvent('on' + type, fn);
// older browsers } else { el['on' + type] = fn; } };
Init-time branching var addListener;
if (typeof window.addEventListener === 'function') { addListener = function(el, type, fn) { el.addEventListener(type, fn, false); }; } else if (typeof document.attachEvent === 'function') { addListener = function(el, type, fn) { el.attachEvent('on' + type, fn); }; } else { addListener = function(el, type, fn) { el['on' + type] = fn; }; }
More object creation patterns
Private/privileged function MyConstr() { var a = 1; this.sayAh = function() { return a; }; }
>>> new MyConstr().sayAh(); 1
Declaring dependencies YAHOO.properties.foo = function() { var Event = YAHOO.util.Event, Dom = YAHOO.util.Dom;
// ...
}();
Namespacing
var myApp = {}; myApp.someObj = {}; myApp.someObj.someFunc = function(){};
Namespacing
function namespace(){…}
>>> namespace(‘my.name.space’) >>> typeof my.name.space “object”
Chaining var o = { v: 1, increment: function() { this.v++; return this; }, add: function(v) { this.v += v; return this; }, shout: function(){ alert(this.v); } };
>>> o.increment().add(3).shout() // 5
Configuration objects myPerson(last, first, dob, address)
vs.
var conf = { first: 'Bruce', last: 'Wayne' }; myPerson(conf);
Static members – public
function MyMath() { // math here... }
MyMath.PI = 3.14; MyMath.E = 2.7;
Module pattern MYAPP.namespace('modules.foo'); MYAPP.modules.foo = function() { var a = 1; return { getA: function(){ return a; } }; }();
Design Patterns
Singleton
var mysingleton = {};
• It’s as simple as that
Singleton v.2 (classical)
var my1 = new Single(); var my2 = new Single(); >>> my1 === my2 true
• How to make this work?
Singleton v.2 - option 1
var Single = function() { if (typeof Single.instance === “object”) { return Single.instance; } Single.instance = this; };
• Drawback – the instance property is public
… option 2 (closure)
function Single() {
var instance = this;
// add more to this…
Single = function (){ return instance; }; }
Factory
var Polygon = function() {}; var triangle = Polygon.factory(‘Triangle’); var circle = Polygon.factory(‘Circle’);
Polygon.Triangle = function(){}; Polygon.Circle = function(){};
Polygon.factory = function(name) { if (typeof Polygon[name] === “function”) { return new Polygon[name](); } };
And one more thing…
Run JSLint
• http://jslint.com • Integrate with your editor
JSLint
• missing semi-colons • missing curly brackets • undeclared vars • trailing commas • unreachable code • for-in loops • …
Thank you!
More…
http://jspatterns.com http://slideshare.net/stoyan http://jsmag.com column