Optimizing JavaScript for Mobile
http://goo.gl/zuuEIU
Who am I?
Anzor Bashkhaz - @anzor_b
Developer Relations at BlackBerry
Memory
var greeting = "hello";
var greeting2 = greeting;
greeting = null;
console.log(greeting2);
> "hello"
Memory
In JavaScript, all variables are pointers to memory addresses.
var greeting = "hello";
//greeting points at a memory address with string "hello"
var greeting2 = greeting2;
//greeting2 points at the same memory address with string "hello"
var greeting = null;
//removes (dereferences) "greeting" pointer to "hello"
Memory
Memory types in JavaScript
- boolean
- number
- string
- object
true or false
any double precision number (IEEE 754)
UTF-16 (UCS-2) string
key value map/ dictionary
Memory
7: "hello"
5: greeting
6: greeting2
5: greeting
6: greeting2
Memory
So how does the browser reclaim back its memory?
Garbage Collection
Garbage refers to nodes that no longer have pointers referencing them.
greeting = null;
greeting2 = null;
//memory occupied by string "hello" can now be reclaimed (GC'd).
greeting2 = null;
//memory occupied by string "hello" can now be reclaimed (GC'd).
Memory
Nodes 9 and 10 are no longer referenced from the root, and can therefore be cleaned up.
More aggressive on mobile, due to memory constraints.
Tools
Chrome Task Manager (Tools > Task Manager)
Chrome Console> performance.memory
Heap Profiler
Shows memory contents and compares memory between runs.
DOM memory
DOM nodes take up memory even when not attached to DOM tree (detached DOM nodes)
DOM Memory
DOM Memory
Tool: Chrome Developer Tools Timeline Panel
DOM Node count:
Total number of attached and detached DOM nodes at any given time
Event Listener count:
Total number of event listeners at any given time
DOM Memory
-
Common memory leak pattern in modern HTML5 apps
-
create / destroy cycles (eg. View Management)
-
Everything created needs to be destroyed
Example : custom Dialog
Dialog.create = function() { var newDialog = { open : function() {}, close : function() {}, update : function() {}, init : function() { this.el = document.createElement("div"); return this; } }; return newDialog.init(); }; var dialog = Dialog.create(); //returns a new dialog
Create / Destroy
//create 1000 dialogs var dialogs = []; for (var i=0; i<1000; i++){ var dialog = Dialog.create(); dialogs.push(dialog); }; //wait for 5 seconds setTimeout(function(){ //set the dialogs to null for (var i=0; i<1000; i++){ dialogs[i] = null; }; },5000);
DOM graph
Unhealthy DOM memory
Dialog.create = function() { var update = function(){ newDialog.update(); }; var newDialog = { open : function() {}, close : function() {}, update : function() {}, init : function() { this.el = document.createElement("div"); window.addEventListener("orientationchange", update); return this; } }; return newDialog.init(); };
DOM Graph
Unhealthy DOM memory
window.addEventListener("orientationchange", update);
-
window holds a reference to the update function
-
this prevents objects from being GC'd, leaving lingering DOM nodes
aka. Zombie Nodes
Another memory leak pattern
Nodes are not being cleaned up
Preventing Zombie Nodes
a good "destroy" function:
-
dereference all DOM nodes
- remove all Event listeners
newDialog.destroy = function(){ this.el = null; window.removeEventListener("orientationchange", update); };
var dialog = Dialog.create(); dialog.destroy(); dialog = null; //destroy ensures browser gets all the memory back
Manual GC
Desktop browsers are less aggressive than mobile
This forces garbage collection.
DOM operations
(eg: appendChild, removeChild, replaceChild)
DOM operations are expensive .
But, inevitable.
Reflows
A reflow occurs when:
- A DOM element is modified (class, height, width, etc...)
- Browser needs to traverse to the DOM tree
- And recalculate potential changes to all elements in the tree
Reflows are
expensive
(eg. cause jitters in animations)
Reflows
Chrome devTools' Timeline panel shows reflows
Reflows
How to minimize reflows?
- Batch DOM operations (documentFragment)
- Use "off-flow" animations
- Minimize CSS rules and remove unused CSS rules
Document Fragments
Task: create 100 DOM nodes and append to body
for (var i=0; i< 100;i++){ var el = document.createElement("div"); document.body.appendChild(el); };
100 live DOM appendChild operations = very expensive
Document Fragments
document.createDocumentFragment();
Allows developers to create a set of DOM nodes off live DOM and append once to live DOM.
Document Fragments
var fragment = document.createDocumentFragment(); for (var i=0; i< 100;i++){ var el = document.createElement("div"); fragment.appendChild(el); }; document.body.appendChild(fragment);
single live DOM operation = much more efficient
also, a single reflow, versus 100
Animations
-
Use hardware accelerated animations.
- transform: translate3d(x,y,z);
-
If possible, animate elements with position: absolute
- this has a smaller effect on sibling elements.
Event Delegation
In lists and grids, it is not uncommon to bind each item to an Event Listener.
var listEl = document.createElement("div"); for (var i = 0; i < 1000; i++) { var el = document.createElement("div"); el.setAttribute("data-index", i); el.addEventListener("click", function() { console.log(this.getAttribute("data-index")); }); listEl.appendChild(el); };
Event Delegation
Event Delegation
Instead, why not attach a single listener to the parent element and use Event Bubbling?
var listEl = document.createElement("div"); for (var i = 0; i < 1000; i++) { var el = document.createElement("div"); el.setAttribute("data-index", i); listEl.appendChild(el); }; listEl.addEventListener("click", function(e) { console.log(e.target.getAttribute("data-index")); });
Event Delegation
Event Delegation
That's one event listener versus 1000.
Event bubbling (e) provides the target (e.target) element that first captured the event.
Additional tips
Avoid
What's wrong with the following?
for (
i
= 0; i < 100; i++){
//do something
};
Avoid
-
var start = function(){
//do something
};
var start = function start(){
//do something
};
//do something
};
-
setTimeout(function(){
//do something
},100);
setTimeout(function doSomething(){
//do something
},100);
//do something
},100);
Avoid
Unnecessary CSS selectors:
div#header { } - instead, just use #header
body * { } - '*' is very expensive
Smooth scrolling
overflow: scroll;
-webkit-overflow-scrolling: touch;
-webkit-overflow-scrolling: touch;
Provides hardware accelerated, inertia scrolling on BB10 + iOS :)
What about the following?
-webkit-overflow-scrolling: -blackberry-touch;
-webkit-overflow-scrolling: -blackberry-touch;
References
JavaScript Memory Profiling (developer.google.com)
http://goo.gl/OOtOLQ
JavaScript Profiling (Smashing Magazine)
http://goo.gl/vS3tuO
Finding and debugging memory leaks in JavaScript with Chrome DevTools (Gonzalo Ruiz de Villa)
Memory Profiling with Chrome DevTools (YouTube)
Optimizing javascript for mobile
By Anzor B
Optimizing javascript for mobile
- 1,437