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"
<!doctype html>!<html>! <head>! <title>My Awesome App</title>! <script src="App.js"></script>! </head>! <body>! </body>!</html>"
3"
4"
What is maintainability?
5"
In engineering, maintainability is the ease with which a product can be maintained in order to...
h(p://en.wikipedia.org/wiki/Maintainability"
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"
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"
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"
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"
10"
It's about making our lives easier
11"
It's about making our work pass the test of time
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/2010Efirst/demo/750"
13"
14"
• Mobile Web Team - 4 developers
15"
• Mobile Web Team - 4 developers • JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver
16"
• Mobile Web Team - 4 developers • JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver • 60,000+ lines of JavaScript
17"
• Mobile Web Team - 4 developers • JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver • 60,000+ lines of JavaScript • ~500,000 daily active users
18"
• Mobile Web Team - 4 developers • JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver • 60,000+ lines of JavaScript • ~500,000 daily active users • Code maintainability is key!
var topics = [! 'Type Checking',! 'Classes and Inheritance’,! 'Asynchronous Code',! 'Performance'!];!" 19"
topics.shift();!"Type Checking" !
!
20"
<!doctype html>!<html>! <head>! <title>My Awesome App</title>! <script src="App.js"></script>! </head>! <body>! </body>!</html>"
21"
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"
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"
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"
25"
Exceptions
if (data.value) {! callback(data.value);!}!!!TypeError: Cannot read property 'value' of null !!!!!"""
26"
if (data && data.callback) {! var result = data.callback();!}!!!TypeError: Property 'callback' of object #<Object> !is not a function""
27"
if (data && data.value) {! var index = data.value.indexOf('something');!}!!!TypeError: Object #<Object> has no method ‘indexOf’!!!!!!""
28"
typeof {};!"object" !!typeof 'hello';!"string" !!typeof 5;!"number” !
typeof function () {};!"function" !!typeof undefined;!"undefined" !!typeof true;!"boolean" !!
29"
typeof [];!"object" !!typeof null;!"object" !!typeof new Date();!"object" !
typeof /jsconf/;!"object" !!typeof document.body; !"object" !!typeof NaN; !"number" !!
30"
Object.prototype.toString()!!"
""
31"
Object.prototype.toString()!!"
""
32"
WHY?
• If the this value is undefined, return "[object Undefined]". !
!
33"
• If the this value is undefined, return "[object Undefined]". !
• If the this value is null, return "[object Null]". !
!
34"
• 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"
• 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"
• 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()!
var toString = Object.prototype.toString;!var regex = /\[object (.*?)\]/;!!var type = function (o) {! var match = toString.call(o).match(regex);! return match[1].toLowerCase();!};!
38"
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"
type({});!"object" !!type('hello');!"string" !!type(5);!"number” !
type(function () {});!"function" !!type(undefined);!"undefined" !!type(true);!"boolean" !!
40"
type([]);!"array" !!type(null);!"null" !!type(new Date());!"date" !
type(/jsconf/);!"regex" !!type(document.body);!"htmlbodyelement" !!type(NaN);!"number" !!
41"
type([]); !"array" !!type(null); !"null" !!type(new Date()); !"date" !
type(/jsconf/); !"regex" !!type(document.body);!"htmlbodyelement" !!type(NaN);!"number" !!
42"
???
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"
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
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
46"
Now what?
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"
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"
49"
How does this help maintainability?
50"
Prevents unexpected breakdowns
51"
Prevents unexpected breakdowns Makes future maintenance easier
topics.shift();!"Classes and Inheritance" !
!
52"
53"
Why classes?
54"
var Controller = {!! init: function () {! // do some initialization! },!! loadView: function () {!! }!!};!!// somewhere else in the app!Controller.init();!Controller.loadView();"
55"
var Controller = {!! init: function () {! // do some initialization! },!! loadView: function () {!! }!!};!!// somewhere else in the app!Controller.init();!Controller.loadView();"
Feels messy
56"
var ChatController = {};!!for (var key in Controller) {! ChatController[key] = Controller;!}!!ChatController.loadView = function () {!! Controller.loadView.apply(this, arguments);! // do some additional stuff!!};!"
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
58"
What do we want?
class Controller {!!!
public Controller () {! // do some initialization! }!
!!! public void loadView () {!! !!! }!
!}!!class ChatController extends Controller {!
!!! public void loadView () {!! ! super.loadView();!! ! // do some additional stuff!! }!!!
}" 59"
class Controller {!! constructor () {! // do some initialization! }!! loadView () {!! }!!}!!class ChatController extends Controller {!! loadView () {! super.loadView();! // do some additional stuff! }!!}" 60"
class Controller!! constructor: () ->! # do some initialization!! loadView: () ->!!!class ChatController extends Controller!! loadView: () ->! super! # do some initialization"
61"
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"
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
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
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"
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
67"
There is no such thing as magic.
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"
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
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
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;!};" 71"
ECMAScript 5
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;!};" 72"
Add shorthand to super
var controller = new Controller();!var chat = new ChatController();!!controller instanceof Controller; // true!!chat instanceof Controller; // true!chat instanceof ChatController; // true"
73"
74"
The rest is about good practice
Use getter and setter methods
alert(jon.name);!jon.name = 'John';!!!alert(jon.getName());!jon.setName('John');!jon.set('name', 'John');"
75"
Define all properties on the prototype, even if they are null.
/**! * The persons age.! * @type {Number}! */!Person.prototype.age = null;"
76"
Mark private methods with a leading or trailing underscore
// somethings are best kept private :)!Person.prototype._singInShower = function () {!!};"
77"
Use static methods / properties
Person.prototype.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'!};!!Person.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'!};"
78"
79"
How does this help maintainability?
80"
Correct defects or their causes
81"
Correct defects or their causes Makes future maintenance easier
topics.shift();!"Asynchronous Code" !
!
82"
83"
The big question...
Callbacks or
Promises 84"
Promises • Requires a library to provide the functionality
85"
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
86"
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… 87"
h(p://promisesEaplus.github.io/promisesEspec/"88"
89"
TL;DR
90"
But that must mean...
Callback Hell
91"
92"
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));! }!!"
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));! }!!"
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));! }!!"
Action
Error Handling
95"
No.
“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/ 96"
doSomething(function (err, response) {!!});"
97"
98"
var handler = function (err, response) {!!};!!doSomething(handler);"
99"
! 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();! }!
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();! }!
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();! }!
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();! }!
Reusable
Avoid anonymous functions
103"
Avoid anonymous functions
104"
Useless stack traces
Avoid anonymous functions
105"
Useless stack traces
Sign of poor structure
Keep things shallow
106"
Keep things shallow
107"
Means you are probably using anonymous functions
Keep things shallow
108"
Means you are probably using anonymous functions
Everyone will hate you
109"
How does this help maintainability?
110"
Isolate defects or their causes
111"
Isolate defects or their causes Makes future maintenance easier
112"
Isolate defects or their causes Makes future maintenance easier Prevent unexpected breakdowns
topics.shift();!"Performance" !
!
113"
var i = 0;!var thing;!for (; i < things.length; i++) {! thing = things[i];!}"
things.forEach(function (thing, i) {!!});"
114"
or…
115"http://jsperf.com/foreachvsloop
24x faster
13x faster
13x Faster 350,000 operations per second
$('a').on('click', function (e) {!!});!!!!!!$('#container').on('click', 'a', function (e) {!!});"
or…
116"
117"http://jsperf.com/domevents
21x faster
19x faster 21x Faster
Only 1000 operations per second
118"
$('#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…
119"http://jsperf.com/renderinghtml
48x Faster
44x faster
15x faster Less than 200 operations per second!
DOM operations
120"
DOM operations Iteration / function calls
121"
122"
Beware of premature optimizations
123"
Bottlenecks
var cache = {!! get: function (key) {! return localStorage.getItem(key);! },!! set: function (key, value) {! localStorage.setItem(key, value);! }!!};"
124"
var cache = {!! get: function (key) {! return localStorage.getItem(key);! },!! set: function (key, value) {! localStorage.setItem(key, value);! }!!};"
125"
Disc IO
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);! }!};" 126"
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"
Memory
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"
Quicker reading
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"
Saving for later
130"http://jsperf.com/localstoragevsmemory
3x faster
About the same
131"
How does this help maintainability?
132"
Makes future maintenance easier
133"
Makes future maintenance easier Prevent unexpected breakdowns
134"
topics.shift();!undefined"
135"
What is maintainability?
136"
It's about making our lives easier
137"
It's about making our work pass the test of time
Thank you!
138"
Yet Another Conference 2013
@jonbretman jonbretman.co.uk
Mobile Web Developer @ Badoo
http://techblog.badoo.com @BadooTech
http://habrahabr.ru/company/badoo
@BadooDev