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
true or false
- number
any double precision number (IEEE 754)
- string
UTF-16 (UCS-2) string
- object
key value map/ dictionary
Memory
7: "hello"
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).
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
var el = document.createElement("div")
DOM nodes take up memory even when not attached to DOM tree (detached DOM nodes)
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);
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
That's one event listener versus 1000.
Event bubbling (e) provides the target (e.target) element that first captured the event.