Upload
stoyan-stefanov
View
28.282
Download
13
Embed Size (px)
DESCRIPTION
Slides from Ajax Experience 2009. In this session: - Object creation patterns - Code reuse patterns - Functional patterns - More on object creation - Design patterns Some example patterns: object creation with literals and constructos, prototypes, inheritance and other code reuse patterns, lazy definition, callbacks, singleton, factory, classical and prototypal inheritance, namespaces, chaining, modules, static methods, private and privileged members For more information, see: http://jspatterns.com My column in the JavaScript Magazine (http://jsmag.com) Blog: http://phpied.com
Citation preview
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