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