Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Maintainable JavaScript

Writing Maintainable JavaScript

The second incarnation of 'Ask Not What JavaScript Can Do For You'. I gave this talk at Yet Another Conference in Moscow on October 2nd 2013.

Jon Bretman

October 08, 2013
Tweet

More Decks by Jon Bretman

Other Decks in Programming

Transcript

  1. WRITING MAINTAINABLE JAVASCRIPT Yet Another Conference 2013 @jonbretman jonbretman.co.uk Mobile

    Web Developer @ Badoo http://techblog.badoo.com @BadooTech http://habrahabr.ru/company/badoo @BadooDev 1  
  2. 5   In engineering, maintainability is the ease with which

    a product can be maintained in order to... h(p://en.wikipedia.org/wiki/Maintainability  
  3. 6   In engineering, maintainability is the ease with which

    a product can be maintained in order to... isolate defects or their cause h(p://en.wikipedia.org/wiki/Maintainability  
  4. 7   In engineering, maintainability is the ease with which

    a product can be maintained in order to... correct defects or their cause h(p://en.wikipedia.org/wiki/Maintainability  
  5. 8   In engineering, maintainability is the ease with which

    a product can be maintained in order to... prevent unexpected breakdowns h(p://en.wikipedia.org/wiki/Maintainability  
  6. 9   In engineering, maintainability is the ease with which

    a product can be maintained in order to... make future maintenance easier h(p://en.wikipedia.org/wiki/Maintainability  
  7. 12   for(B=i=y=u=b=i=5-5,x=10,I=[],l=[];B++<304;I[B-1]=B%x?B/x%x<2|B%x<2?7:B/x&4?! 0:l[i++]="ECDFBDCEAAAAAAAAIIIIIIIIMKLNJLKM@G@TSb~?A6J57IKJT576,+-48HLSUmgukgg " +! "OJNMLK IDHGFE".charCodeAt(y++)-64:7);function X(c,h,e,s){c^=8;for(var o,!

    S,C,A,R,T,G,d=e&&X(c,0)>1e4,n,N=-1e8,O=20,K=78-h<<9;++O<99;)if((o=I[T=O])&&! (G=o^c)<7){A=G--&2?8:4;C=o-9?l[61+G]:49;do if(!(R=I[T+=l[C]])&&!!G|A<3||! (R+1^c)>9&&G|A>2){if(!(R-2&7))return K;n=G|(c?T>29:T<91)?o:6^c;S=! (R&&l[R&7|32]*2-h-G)+(n-o?110:!G&&(A<2)+1);if(e>h||1<e&e==h&&S>2|d)! {I[T]=n;I[O]=0;S-=X(c,h+1,e,S-N);if(!(h||e-1|B-O|T-b|S<-1e4))return W(),! c&&setTimeout("X(8,0,2),X(8,0,1)",75);I[O]=o;I[T]=R}if(S>N||!h&S==N&&! Math.random()<.5)if(N=S,e>1)if(h?s-S<0:(B=O,b=T,0))break}while(!R&G>2||(T=O,! (G||A>2|(c?O>78:O<41)&!R)&&++C*--A))}return-K+768<N|d&&N}function W(){! i="<table>";for(u=18;u<99;document.body.innerHTML=i+=++u%x-9?! "<th width=60 height=60 onclick='I[b="+u+"]>8?W():X(0,0,1)'style='font-size:50px'bgcolor=#"! +(u-B?u*.9&1||9:"d")+"0f0e0>&#"+(I[u]?9808+l[67+I[u]]:160):u++&&"<tr>")B=b}W()   h(p://js1k.com/2010-­‐first/demo/750  
  8. 15   •  Team of 4 developers •  JavaScript, jsDoc,

    JSHint, Closure Compiler, JsTestDriver
  9. 16   •  Team of 4 developers •  JavaScript, jsDoc,

    JSHint, Closure Compiler, JsTestDriver •  60,000+ lines of JavaScript
  10. 17   •  Team of 4 developers •  JavaScript, jsDoc,

    JSHint, Closure Compiler, JsTestDriver •  60,000+ lines of JavaScript •  ~500,000 daily active users
  11. 18   •  Team of 4 developers •  JavaScript, jsDoc,

    JSHint, Closure Compiler, JsTestDriver •  60,000+ lines of JavaScript •  ~500,000 daily active users •  Code maintainability is key!
  12. Api.get('/conversations', function (conversations) {! ! var intros = conversations.map(function (c)

    {! var name = c.theirName;! var mostRecent = c.messages[0].text.substring(0, 30);! return name + ': ' + mostRecent;! });! ! App.renderMessages(intros);! ! });! 22  
  13. Api.get('/conversations', function (conversations) {! ! var intros = conversations.map(function (c)

    {! var name = c.theirName;! var mostRecent = c.messages[0].text.substring(0, 30);! return name + ': ' + mostRecent;! });! ! App.renderMessages(intros);! ! });! 23  
  14. Api.get('/conversations', function (err, conversations) {! ! var intros = conversations.map(function

    (c) {! var name = c.theirName;! var mostRecent = c.messages[0].text.substring(0, 30);! return name + ': ' + mostRecent;! });! ! App.renderMessages(intros);! ! });! A lot of things have to go right here 24  
  15. if (data.value) {! callback(data.value);! }! ! ! TypeError: Cannot read

    property 'value' of null! ! ! ! !       26  
  16. if (data && data.callback) {! var result = data.callback();! }!

    ! ! TypeError: Property 'callback' of object #<Object> is not a function     27  
  17. if (data && data.value) {! var index = data.value.indexOf('something');! }!

    ! ! TypeError: Object #<Object> has no method ‘indexOf’ ! ! ! ! !     28  
  18. typeof {};! "object"! ! typeof 'hello';! "string"! ! typeof 5;!

    "number”! typeof function () {};! "function"! ! typeof undefined;! "undefined"! ! typeof true;! "boolean"! ! 29  
  19. typeof [];! "object"! ! typeof null;! "object"! ! typeof new

    Date();! "object"! typeof /jsconf/;! "object"! ! typeof document.body;! "object"! ! typeof NaN;! "number"! ! 30  
  20. •  If the this value is undefined, return "[object Undefined]".!

    •  If the this value is null, return "[object Null]".! ! 34  
  21. •  If the this value is undefined, return "[object Undefined]".!

    •  If the this value is null, return "[object Null]".! •  Let class be the value of the [[Class]] property of this. ! 35  
  22. •  If the this value is undefined, return "[object Undefined]".!

    •  If the this value is null, return "[object Null]".! •  Let class be the value of the [[Class]] property of this. •  Return the String value that is the result of concatenating the three Strings "[object ", class, and "]". ! 36  
  23. •  If the this value is undefined, return "[object Undefined]".!

    •  If the this value is null, return "[object Null]".! •  Let class be the value of the [[Class]] property of this. •  Return the String value that is the result of concatenating the three Strings "[object ", class, and "]". ! 37   Function.prototype.call()! or! Function.prototype.apply()!
  24. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! var match = toString.call(o).match(regex);! return match[1].toLowerCase();! };! 38  
  25. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! var match = toString.call(o).match(regex);! return match[1].toLowerCase();! };! 39   this === o  
  26. type({});! "object"! ! type('hello');! "string"! ! type(5);! "number”! type(function ()

    {});! "function"! ! type(undefined);! "undefined"! ! type(true);! "boolean"! ! 40  
  27. type([]);! "array"! ! type(null);! "null"! ! type(new Date());! "date"! type(/jsconf/);!

    "regex"! ! type(document.body);! "htmlbodyelement"! ! type(NaN);! "number"! ! 41  
  28. type([]);! "array"! ! type(null);! "null"! ! type(new Date());! "date"! type(/jsconf/);!

    "regex"! ! type(document.body);! "htmlbodyelement"! ! type(NaN);! "number"! ! ??? 42  
  29. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! ! if (o && o.nodeType === 1) {! return 'element';! }! ! var match = toString.call(o).match(regex);! var _type = match[1].toLowerCase();! ! if (_type === 'number' && isNaN(o)) {! return 'nan';! }! ! return _type;! };! 43  
  30. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! ! if (o && o.nodeType === 1) {! return 'element';! }! ! var match = toString.call(o).match(regex);! var _type = match[1].toLowerCase();! ! if (_type === 'number' && isNaN(o)) {! return 'nan';! }! ! return _type;! };! 44   Special case for DOM elements
  31. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! ! if (o && o.nodeType === 1) {! return 'element';! }! ! var match = toString.call(o).match(regex);! var _type = match[1].toLowerCase();! ! if (_type === 'number' && isNaN(o)) {! return 'nan';! }! ! return _type;! };! 45   Special case for NaN
  32. Api.get('/conversations', function (conversations) {! ! if (type(conversations) !== 'array') {!

    App.renderMessages([]);! return;! }! ! var intros = conversations.map(function (c) {! ! if (type(c) !== 'object') {! return '';! }! ! var name = type(c.theirName) === 'string' ? c.theirName : '';! var mostRecent = '';! ! if (type(c.messages) === 'array' ||! type(c.messages[0]) === 'object' ||! type(c.messages[0].text) === 'string') {! mostRecent = c.messages[0].text.substring(0, 30);! }! ! return name + ': ' + mostRecent;! });! ! App.renderMessages(intros);! ! });   47  
  33. Api.get("/conversations",function(e){var t=e.map(function(e) {var t=e.theirName;var n=e.messages[0].text.substring(0,30);return t+": "+n});App.renderMessages(t)})! ! ! !

    ! ! ! ! ! ! Api.get("/conversations",function(e){if(type(e)!=="array") {App.renderMessages([]);return}var t=e.map(function(e) {if(type(e)!=="object"){return""}var t=type(e.theirName)==="string"?e.theirName:"";var n="";if(type(e.messages)==="array"|| type(e.messages[0])==="object"|| type(e.messages[0].text)==="string") {n=e.messages[0].text.substring(0,30)}return t+": "+n});App.renderMessages(t)})   + 137% 48  
  34. 54   var Controller = {! ! init: function ()

    {! // do some initialization! },! ! loadView: function () {! ! }! ! };! ! // somewhere else in the app! Controller.init();! Controller.loadView();  
  35. 55   var Controller = {! ! init: function ()

    {! // do some initialization! },! ! loadView: function () {! ! }! ! };! ! // somewhere else in the app! Controller.init();! Controller.loadView();   Feels messy
  36. 56   var ChatController = {};! ! for (var key

    in Controller) {! ChatController[key] = Controller;! }! ! ChatController.loadView = function () {! ! Controller.loadView.apply(this, arguments); // do some additional stuff! ! };!  
  37. 57   var ChatController = {};! ! for (var key

    in Controller) {! ChatController[key] = Controller;! }! ! ChatController.loadView = function () {! ! Controller.loadView.apply(this, arguments); // do some additional stuff! ! };!   Not proper inheritance
  38. class Controller {! !! public Controller () {! // do

    some initialization! }! !! ! public void loadView () {! ! !! ! }! ! }! ! class ChatController extends Controller {! !! ! public void loadView () {! ! ! super.loadView();! ! ! // do some additional stuff! ! }! !! }   59  
  39. class Controller {! ! constructor () {! // do some

    initialization! }! ! loadView () {! ! }! ! }! ! class ChatController extends Controller {! ! loadView () {! super.loadView();! // do some additional stuff! }! ! }   60  
  40. class Controller! ! constructor: () ->! # do some initialization!

    ! loadView: () ->! ! ! class ChatController extends Controller! ! loadView: () ->! super! # do some initialization   61  
  41. var ChatController, Controller, _ref,! __hasProp = {}.hasOwnProperty,! __extends = function(child,

    parent) {! for (var key in parent) {! if (__hasProp.call(parent, key))! child[key] = parent[key];! }! function ctor() {! this.constructor = child;! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child.__super__ = parent.prototype;! return child;! };! ! Controller = (function() {! function Controller() {}! Controller.prototype.loadView = function() {};! return Controller;! })();! ! ChatController = (function(_super) {! __extends(ChatController, _super);! ! function ChatController() {! _ref = ChatController.__super__.constructor.apply(this, arguments);! return _ref;! }! ! ChatController.prototype.loadView = function() {! return ChatController.__super__.loadView.apply(this, arguments);! };! ! return ChatController;! })(Controller);   62  
  42. var ChatController, Controller, _ref,! __hasProp = {}.hasOwnProperty,! __extends = function(child,

    parent) {! for (var key in parent) {! if (__hasProp.call(parent, key))! child[key] = parent[key];! }! function ctor() {! this.constructor = child;! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child.__super__ = parent.prototype;! return child;! };! ! Controller = (function() {! function Controller() {}! Controller.prototype.loadView = function() {};! return Controller;! })();! ! ChatController = (function(_super) {! __extends(ChatController, _super);! ! function ChatController() {! _ref = ChatController.__super__.constructor.apply(this, arguments);! return _ref;! }! ! ChatController.prototype.loadView = function() {! return ChatController.__super__.loadView.apply(this, arguments);! };! ! return ChatController;! })(Controller);   63   Utility method
  43. var ChatController, Controller, _ref,! __hasProp = {}.hasOwnProperty,! __extends = function(child,

    parent) {! for (var key in parent) {! if (__hasProp.call(parent, key))! child[key] = parent[key];! }! function ctor() {! this.constructor = child;! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child.__super__ = parent.prototype;! return child;! };! ! Controller = (function() {! function Controller() {}! Controller.prototype.loadView = function() {};! return Controller;! })();! ! ChatController = (function(_super) {! __extends(ChatController, _super);! ! function ChatController() {! _ref = ChatController.__super__.constructor.apply(this, arguments);! return _ref;! }! ! ChatController.prototype.loadView = function() {! return ChatController.__super__.loadView.apply(this, arguments);! };! ! return ChatController;! })(Controller);   64   Class Definitions
  44. var Controller = function () {! // do some initialization!

    };! ! Controller.prototype.loadView = function() {! ! };! ! var ChatController = function (name) {! Controller.apply(this, arguments);! };! ! ChatController.prototype.loadView = function () {! ChatController._super.loadView.apply(this, arguments);! // do some additional stuff! ! }! ! extends(ChatController, Controller);   65  
  45. var Controller = function () {! // do some initialization!

    };! ! Controller.prototype.loadView = function() {! ! };! ! var ChatController = function (name) {! Controller.apply(this, arguments);! };! ! ChatController.prototype.loadView = function () {! ChatController._super.loadView.apply(this, arguments);! // do some additional stuff! ! }! ! extends(ChatController, Controller);   66   The magic bit
  46. var extends = function(child, parent) {! for (var key in

    parent) {! if (parent.hasOwnProperty(key)) {! child[key] = parent[key];! }! }! function ctor() { ! this.constructor = child; ! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child._super = parent.prototype;! return child;! };   68  
  47. var extends = function(child, parent) {! for (var key in

    parent) {! if (parent.hasOwnProperty(key)) {! child[key] = parent[key];! }! }! function ctor() { ! this.constructor = child; ! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child._super = parent.prototype;! return child;! };   69   Copy static properties / methods
  48. var extends = function(child, parent) {! for (var key in

    parent) {! if (parent.hasOwnProperty(key)) {! child[key] = parent[key];! }! }! function ctor() { ! this.constructor = child; ! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child._super = parent.prototype;! return child;! };   70   Set up prototype chain
  49. var extends = function(child, parent) {! for (var key in

    parent) {! if (parent.hasOwnProperty(key)) {! child[key] = parent[key];! }! }! function ctor() { ! this.constructor = child; ! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child._super = parent.prototype;! return child;! };   71   Set up prototype chain (kind of a hack)
  50. var extends = function(child, parent) {! for (var key in

    parent) {! if (parent.hasOwnProperty(key)) {! child[key] = parent[key];! }! }! ! ! ctor.prototype = Object.create(parent.prototype);! ! ! child._super = parent.prototype;! return child;! };   72   ECMAScript 5
  51. var extends = function(child, parent) {! for (var key in

    parent) {! if (parent.hasOwnProperty(key)) {! child[key] = parent[key];! }! }! function ctor() { ! this.constructor = child; ! }! ctor.prototype = parent.prototype;! child.prototype = new ctor();! child._super = parent.prototype;! return child;! };   73   Add shorthand to super
  52. var controller = new Controller();! var chat = new ChatController();!

    ! controller instanceof Controller; // true! ! chat instanceof Controller; // true! chat instanceof ChatController; // true   74  
  53. Use getter and setter methods alert(jon.name);! jon.name = 'John';! !

    ! alert(jon.getName());! jon.setName('John');! jon.set('name', 'John');   76  
  54. Define all properties on the prototype, even if they are

    null. /**! * The persons age.! * @type {Number}! */! Person.prototype.age = null;   77  
  55. Mark private methods with a leading or trailing underscore //

    somethings are best kept private :)! Person.prototype._singInShower = function () {! ! };   78  
  56. Use static methods / properties when appropriate Person.prototype.EVENTS = {!

    WALK: 'WALK',! TALK: 'TALK'! };! ! Person.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'! };   79  
  57. Promises •  Requires a library to provide the functionality • 

    Different implementations •  jQuery Deferred api.jquery.com/category/deferred-object/ •  rsvp.js github.com/tildeio/rsvp.js •  when.js github.com/cujojs/when •  promise.js github.com/then/promise 87  
  58. Promises •  Requires a library to provide the functionality • 

    Different implementations •  jQuery Deferred api.jquery.com/category/deferred-object/ •  rsvp.js github.com/tildeio/rsvp.js •  when.js github.com/cujojs/when •  promise.js github.com/then/promise •  Kind of complicated… 88  
  59. 93   load: function () {! ! Api.get('/profile/own', _.bind(function (ownProfile)

    {! ! this.ownProfile = ownProfile;! ! Api.get('/profile/' + id, _.bind(function (theirProfile) {! ! this.theirProfile = theirProfile;! ! Api.get('/chatMessages', _.bind(function (messages) {! ! this.messages = messages;! this.render();! ! }, this), _.bind(function (err) {! this.onError();! }, this));! }, this), _.bind(function (err) {! this.onError();! }, this));! }, this), _.bind(function (err) {! this.onError();! }, this));! }! !
  60. 94   load: function () {! ! Api.get('/profile/own', _.bind(function (ownProfile)

    {! ! this.ownProfile = ownProfile;! ! Api.get('/profile/' + id, _.bind(function (theirProfile) {! ! this.theirProfile = theirProfile;! ! Api.get('/chatMessages', _.bind(function (messages) {! ! this.messages = messages;! this.render();! ! }, this), _.bind(function (err) {! this.onError();! }, this));! }, this), _.bind(function (err) {! this.onError();! }, this));! }, this), _.bind(function (err) {! this.onError();! }, this));! }! !
  61. 95   load: function () {! ! Api.get('/profile/own', _.bind(function (ownProfile)

    {! ! this.ownProfile = ownProfile;! ! Api.get('/profile/' + id, _.bind(function (theirProfile) {! ! this.theirProfile = theirProfile;! ! Api.get('/chatMessages', _.bind(function (messages) {! ! this.messages = messages;! this.render();! ! }, this), _.bind(function (err) {! this.onError();! }, this));! }, this), _.bind(function (err) {! this.onError();! }, this));! }, this), _.bind(function (err) {! this.onError();! }, this));! }! ! Action Error Handling
  62. “I’ve come to the conclusion that callback hell is a

    design choice and not an inherent flaw in the concept of asynchronous function and callback” http://blog.caplin.com/2013/03/13/callback-hell-is-a-design-choice/ 97  
  63. 99   var handler = function (err, response) {! !

    };! ! doSomething(handler);  
  64. 100   ! load: function (id) {! this.id = id;!

    Api.get('/profile/own', this.onOwnProfile);! },! ! onOwnProfile: function (err, ownProfile) {! if (err) return this.onError();! this.ownProfile = ownProfile;! Api.get('/profile/' + this.id, this.onTheirProfile);! },! ! onTheirProfile: function (err, theirProfile) {! if (err) return this.onError();! this.theirProfile = theirProfile;! Api.get('/chatMessages', this.onMessages);! },! ! onMessages: function (err, messages) {! if (err) return this.onError();! this.messages = messages;! this.render();! }!
  65. 101   ! load: function (id) {! this.id = id;!

    Api.get('/profile/own', this.onOwnProfile);! },! ! onOwnProfile: function (err, ownProfile) {! if (err) return this.onError();! this.ownProfile = ownProfile;! Api.get('/profile/' + this.id, this.onTheirProfile);! },! ! onTheirProfile: function (err, theirProfile) {! if (err) return this.onError();! this.theirProfile = theirProfile;! Api.get('/chatMessages', this.onMessages);! },! ! onMessages: function (err, messages) {! if (err) return this.onError();! this.messages = messages;! this.render();! }!
  66. 102   ! load: function (id) {! this.id = id;!

    Api.get('/profile/own', this.onOwnProfile);! },! ! onOwnProfile: function (err, ownProfile) {! if (err) return this.onError();! this.ownProfile = ownProfile;! Api.get('/profile/' + this.id, this.onTheirProfile);! },! ! onTheirProfile: function (err, theirProfile) {! if (err) return this.onError();! this.theirProfile = theirProfile;! Api.get('/chatMessages', this.onMessages);! },! ! onMessages: function (err, messages) {! if (err) return this.onError();! this.messages = messages;! this.render();! }!
  67. 103   ! load: function (id) {! this.id = id;!

    Api.get('/profile/own', this.onOwnProfile);! },! ! onOwnProfile: function (err, ownProfile) {! if (err) return this.onError();! this.ownProfile = ownProfile;! Api.get('/profile/' + this.id, this.onTheirProfile);! },! ! onTheirProfile: function (err, theirProfile) {! if (err) return this.onError();! this.theirProfile = theirProfile;! Api.get('/chatMessages', this.onMessages);! },! ! onMessages: function (err, messages) {! if (err) return this.onError();! this.messages = messages;! this.render();! }! Reusable
  68. Keep things shallow 109   Means you are probably using

    anonymous functions Everyone will hate you
  69. var i = 0;! var thing;! for (; i <

    things.length; i++) {! thing = things[i];! }   things.forEach(function (thing, i) {! ! });   115   or…
  70. $('a').on('click', function (e) {! ! });! ! ! ! !

    ! ! ! ! $('#container').on('click', 'a', function (e) {! ! });   or… 117  
  71. 119   $('#container').append('<ul></ul>');! for (var i = 0; i <

    messages.length; i++) {! $('#container')! .find('ul')! .append('<li>' + messages[i].text + '</li>');! }   ! ! ! ! ! var html = '<ul>';! for (var i = 0; i < messages.length; i++) {! html += '<li>' + messages[i].text + '</li>';! }! html += '</ul>';! $('#container').html(html);! or…
  72. var cache = {! ! get: function (key) {! return

    localStorage.getItem(key);! },! ! set: function (key, value) {! localStorage.setItem(key, value);! }! ! };   125  
  73. var cache = {! ! get: function (key) {! return

    localStorage.getItem(key);! },! ! set: function (key, value) {! localStorage.setItem(key, value);! }! ! };   126   Disc IO
  74. var cache = {! ! data_: {},! ! get: function

    (key) {! ! if (this.data_.hasOwnProperty(key)) {! return this.data_[key];! }! ! var value = localStorage.getItem(key);! ! if (value !== null) {! this.data_[key] = value;! return value;! }! ! return null;! },! ! set: function (key, value) {! this.data_[key] = value;! localStorage.setItem(key, value);! }! ! };   127  
  75. var cache = {! ! data_: {},! ! get: function

    (key) {! ! if (this.data_.hasOwnProperty(key)) {! return this.data_[key];! }! ! var value = localStorage.getItem(key);! ! if (value !== null) {! this.data_[key] = value;! return value;! }! ! return null;! },! ! set: function (key, value) {! this.data_[key] = value;! localStorage.setItem(key, value);! }! ! };   128   Memory
  76. var cache = {! ! data_: {},! ! get: function

    (key) {! ! if (this.data_.hasOwnProperty(key)) {! return this.data_[key];! }! ! var value = localStorage.getItem(key);! ! if (value !== null) {! this.data_[key] = value;! return value;! }! ! return null;! },! ! set: function (key, value) {! this.data_[key] = value;! localStorage.setItem(key, value);! }! ! };   129   Quicker reading
  77. var cache = {! ! data_: {},! ! get: function

    (key) {! ! if (this.data_.hasOwnProperty(key)) {! return this.data_[key];! }! ! var value = localStorage.getItem(key);! ! if (value !== null) {! this.data_[key] = value;! return value;! }! ! return null;! },! ! set: function (key, value) {! this.data_[key] = value;! localStorage.setItem(key, value);! }! ! };   130   Saving for later
  78. Thank you! 139   Yet Another Conference 2013 @jonbretman jonbretman.co.uk

    Mobile Web Developer @ Badoo http://techblog.badoo.com @BadooTech http://habrahabr.ru/company/badoo @BadooDev