You are on page 1of 112

JavaScript

good practices
and tips and tricks
Damian Wielgosik
ferrante.pl
javascript.pl
twitter.com/varjs
remember that performance tips
change over the time as JVM
evolves
make yourself familiar with
ECMAScript
ECMAScript
sometimes it’s difficult...
... to speak in a human language
... znaleźć ludzki
Dmitry język
Soshnikov

your personal ECMAScript teacher


lint your code, hurt your feelings
jslint.com
jshint.com
/*jslint sloppy: true, vars: true, white: true, plusplus: true,
newcap: true */
(function() {
// ...
})();
mount your linting tool to the
text editor/IDE you use
it’s a result of cmd+L in TextMate
go TDD when it’s possible
try Buster.js
(busterjs.org/)
(function() {
if (typeof require == "function" && typeof module ==
"object") {
buster = require("buster");
require("../models/ArticleFactory.js");
}

var assert = buster.assert;


var factory;

buster.testCase("ArticleFactory", {
setUp: function () {
factory = new app.models.ArticleFactory();
},

"createArticle should not return null": function () {


var article = factory.createArticle(1, "Foobar",
"bla bla", "12/12/2012","12:12",
app.models.Artykul.STATUSES.ACTIVE, { addArticle: function()
{}}, {}, {});
assert.isObject(article);
},
});
})();
there is a lot of unit testing
libraries, choose the best
there are great presentations too

slideshare.net/szafranek/practical-guide-to-unit-
testing
use native language parts for
creating objects and arrays

var arr = []; // not new Array


var obj = {}; // not new Object
use self-invoking functions not
to pollute outer scope

(function() {
// do stuff here
})();
use self-invoking functions not
to pollute outer scope

(function() {
var privateVar = 1;
})();
console.log(privateVar); // undefined
define variables at the top of the
scope

var fn = function() {
var news = [],
timer,
foobar;
};
use === everywhere

0 === ""; // false


0 == ""; // true
do not iterate over an array with
for in loop

Array.prototype.contains = function() {
// ...
};

var arr = [1, 2, 3];


for (var i in arr) {
console.log(i); // contains
}
add to an array using push

var arr = [1];


arr.push(2);
arr; // [1,2]
you can store a global object to
avoid mistakes with this

(function() {
var global = this;
console.log(global === window); // true
})();
global compatible with
ECMAScript 5 and „use strict”

"use strict";
var global = Function('return this')();
cache native functions to protect
them

(function() {
var isArray = Array.isArray;
})();
be careful with this

var obj = {
foo : function() {
return this.myVariable;
},
myVariable : 1
};

var fn = obj.foo;
fn(); // undefined
be careful with this - don’t forget
about call/apply

var obj = {
foo : function() {
return this.myVariable;
},
myVariable : 1
};

var fn = obj.foo;
fn.call(obj); // 1
be careful with this - don’t forget
about bind

var obj = {
foo : function() {
return this.myVariable;
},
myVariable : 1
};

var fn = obj.foo.bind(obj);
fn(obj); // 1
not every browser supports
Function.bind
not every browser supports
Function.bind - but you can
polyfill it!
Function.bind polyfill

if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is
not callable");
}

var aArgs = Array.prototype.slice.call(arguments, 1),


fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();

return fBound;
};
}
for loop is not slower than while

jsperf.com/for-loop-research
using indexes with localStorage
is faster than getItem in some
browsers

jsperf.com/localstorage-science
using indexes with localStorage
is faster than getItem in some
browsers

var key = 1;
localStorage[key]; // faster
localStorage.getItem(key); // slower
native forEach is not faster than
classic loops

var arr = [1, 2, 3, 4, 5];


arr.forEach(function() {}); // slower
for (var i = 0, ilen = arr.length; i < ilen; i++) {}; // faster

jsperf.com/for-vs-array-foreach
use requestAnimationFrame if you deal with
heavy proccesses like DOM manipulation

(function() {
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame || function(fn) { window.setTimeout(fn,
16); };
window.requestAnimationFrame = requestAnimationFrame;

var start = Date.now().


var elements = 0;

var step = function() {


var house = document.createElement("div");
house.classList.addClass("house");
document.body.appendChild(building);
elements++;
if (elements < 10000) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
})();
do not create too many functions
- that costs memory

window.setTimeout(function() {
makeThings();
}, 16);

no!

var frame = function() {


makeThings();
};
window.setTimeout(frame, 16);

yes!
functions are objects

var fn = function() {};


fn.myProperty = 1;
fn.myProperty; // 1
functions are objects, so they
help cache

var getSin = function(num) {


if (getSin[num]) {
return getSin[num];
} else {
return getSin[num] = Math.sin(num);
}
};
more crossbrowser isArray?

(function() {
var toString = Object.prototype.toString;
var isArray = Array.isArray || function(arr) { return toString.call
(arr) === "[object Array]"; };
})();
clear arrays using length for
memory performance

var arr = [1, 2, 3, 4, 5];


arr.length = 0; // instead of arr = [];
max number in an array?

var arr = [1, 2, 3, 4, 5];


Math.max.apply(Math, arr);
use ”in” to check if the property
is defined in the object

var obj = {
foobar : ""
};

'foobar' in obj; // true


'barbar' in obj; // false
!!obj.foobar; // false
use typeof to check variable
types

var num = 2;
if (typeof num === "number") {
// we have a number
}
use typeof to check if you can
fire a function

var fn = function() {};


if (typeof fn === "function") {
fn();
}
typeof RegExp

typeof RegExp === "function"; // true


typeof /\d+/g; // "object"
you don’t need jQuery just to get
some nodes

var news = document.querySelectorAll("div.news"); // finds all the elements


var news = document.querySelector("div.news"); // finds the first element
you don’t need regular
expressions to work with class
names

document.body.classList.addClass("foo"); // <body class="foo">


document.body.classList.contains("foo"); // true
document.body.classList.toggle("foo"); // <body>
document.body.classList.toggle("foo"); // <body class="foo">
document.body.classList.remove("foo"); // <body>
build your view templates with
innerHTML

var html = '<ul class="news">' +


'<li>' +
'<h1>Something</h1>' +
'<p>Text</p>' +
'</li>' +
'</ul>';

container.innerHTML = html;
var newsList = container.querySelector(".news");
make use of datasets

<div data-id="123" data-type="news"></div>

node.dataset.id; // "123"
node.dataset.type; // "news"
did you know about
node.matchesSelector?

<div id="foo" class="bar">This is the element!</div>


<script type="text/javascript">
var el = document.getElementById("foo");
if (el.mozMatchesSelector("div.bar")) {
alert("Match!");
}
</script> // thanks to MDN
window.getComputedStyle if
node.style is not enough

<style>
#elem-container{
position: absolute;
left: 100px;
top: 200px;
height: 100px;
}
</style>

<div id="elem-container">dummy</div>
<div id="output"></div>

<script>
function getTheStyle(){
var elem = document.getElementById("elem-container");
var theCSSprop = window.getComputedStyle(elem,null).getPropertyValue("height");
document.getElementById("output").innerHTML = theCSSprop;
}
getTheStyle();
</script>
innerHTML is not the fastest
way to check if node is empty

if (node.innerHTML === "") {} // slower


if (node.childNodes.length === 0) {} // faster
don’t extend DOM nodes with
custom properties to avoid
memory leaks

var div = document.createElement("div");


div.customThing = function() {};
add events using
addEventListener

node.addEventListener("click", function() {
alert("Boom!");
}, false);
do not forget to prevent form
submitting when dealing with
web app forms that are sent e.g.
by AJAX

form.addEventListener("submit", function(e) {
e.preventDefault();
}, false);
Performance?
var start = +new Date();
for (var i = 0; i < 100000; i++);
console.log("Result is: ", +new Date() - start);
Better?
console.time("My test");
for (var i = 0; i < 100000; i++);
console.timeEnd("My test");
Still better?
console.profile("My test");
runApp();
console.profileEnd("My test");
The best?
jsperf.com

jsperf.com
jsperf measures operations per
second!
See also window.performance
do not optimize prematurely!
JavaScript !== Java

do not optimize prematurely!

flickr.com/photos/paulmartincampbell/3583176306/sizes/o/in/photostream/
forget about your old habits!
do not port bad solutions to JavaScript!
otherwise they’re gonna find
you! ;-)

http://www.fotofaza.pl/podglad_zdjecia,15,ludzie,chuligani.html
function calls cost time!
use JS asynchronously when needed
var arr = [ function() { console.log("A"); },
function() { throw new Error("boom!"); },
function() { console.log("B"); },
function() { console.log("C"); }
];

for (var i = 0, ilen = arr.length; i < ilen; i++) {


arr[i]();
}
oops?
var arr = [ function() { console.log("A"); },
function() { throw new Error("boom!"); },
function() { console.log("B"); },
function() { console.log("C"); }
];

for (var i = 0, ilen = arr.length; i < ilen; i++) {


window.setTimeout(arr[i], 0);
}
timers can be useful with AJAX requests
var throttle = function(fn, delay) {
var timer = null;
return function () {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
};

$('input.username').keypress(throttle(function (event) {
// do the Ajax request
}, 250));

http://remysharp.com/2010/07/21/throttling-function-calls/
parseInt(„09”) === 0
JS thinks „09” is an octal number
because it starts with 0
parseInt(„09”, 10) === 9
However,
parseFloat(„09”) === 9
However,
parseFloat(„09”) === 9
document.querySelectorAll("div")
returns a NodeList
not an array
var nodes = document.querySelectorAll("div");
nodes = [].slice.apply(nodes);
However:
„Whether the slice function can be applied successfully to a host object is
implementation-dependent.” - ECMAScript
mobile?
Use window.scrollTo(0, 1) to get rid of the browser address bar
on iOS!
/mobile/i.test(navigator.userAgent) && !location.hash && setTimeout
(function () {
if (!pageYOffset) window.scrollTo(0, 1);
}, 1000);

thanks to amazing work by Remy Sharp


http://remysharp.com/2010/08/05/doing-it-right-skipping-the-
iphone-url-bar/
Mobile debugging?
Aardwolf - mobile
debugging made easy

Yes, we all know Firebug and Web Inspector


Memory stats?
WebKit Inspector

Memory?
Memory leaks?
Memory leak checker
sIEeve

Performance?

home.orange.nl/jsrosman/
Memory leaks?

people.mozilla.com/~dbaron/leak-screencasts/
general thoughts
Get rid of jQuery if it’s not neccessary -
there is http://microjs.com
Hunt on new stuff!
http://js.gd might be a good start!
Visit JSNews on Facebook for more awesomeness
http://tinyurl.com/jsnewspl
or attend meet.js meetups
Poznan, Warsaw, Wroclaw, Cracow
http://meetjs.pl
but first of all, be smart and listen to smart people -
there is a lot on the web

You might also like