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

JavaScript Patterns