I understand that when a view is removed through .remove(), .stopListening() is called on that view to remove any event listeners associated with that view in Backbone. From the Backbone docs:
remove view.remove()
Removes a view from the DOM, and calls stopListening to remove any bound events that the view has listenTo'd.
I have views that are appended to a container that only have events related to dom actions on themselves through Backbone's events hook.
var View = Backbone.View.extend({
events : {
'input keyup' : 'searchDropdown'
},
searchDropdown: function () {
$('dropdown').empty();
//Appends views based on search
}
});
My question is really whether or not I'm leaking any memory (significant or not) when calling $.empty() on a container that effectively removes the view(s) appended inside of it. And if I am, is there any good convention for accessing and calling .remove() on those views?
You don't need any special framework for this but it's a good idea to implement removal properly and not depend on the browser being smart enough to do this. Sometimes in a large app you will find you specifically need to override the remove method to do some special cleanup - for instance you are using a library in that view which has a destroy method.
A modern browser tends to have a GC which is smart enough for most cases but I still prefer not to rely on that. Recently I came on to a project in Backbone which had no concept of subviews and I reduced the leaking nodes by 50% by changing to remove from empty (in Chrome 43). It's very hard to have a large javascript app not leak memory, my advice is to monitor it early on: If a DOM Element is removed, are its listeners also removed from memory?
Watch out for things which leak a lot of memory - like images. I had some code on a project that did something like this:
var image = new Image();
image.onLoad(.. reference `image` ..)
image.src = ...
Basically a pre-loader. And because we weren't explicitly doing image = null the GC never kicked in because the callback was referencing the image variable. On an image heavy site we were leaking 1-2mb with every page transition which was crashing phones. Setting the variable to null in a remove override fixed this.
Calling remove on subviews is as easy as doing something like this:
remove: function() {
this.removeSubviews();
Backbone.View.prototype.remove.call(this);
},
removeSubviews: function() {
if (!_.isEmpty(this.subViews)) {
_.invoke(this.subViews, 'remove');
this.subViews = [];
}
}
You just need to add your subview instances to an array. For example when you create a subview you could have an option like parentView: this and add it to the array of the parent. I have done more intricate subview systems in the past but that would work fine. On initialize of the views you could do something like:
var parentView = this.options.parentView;
if (parentView) {
(parentView.subViews = parentView.subViews || []).push(this);
}
Related
jQuery holds references to DOM nodes in its internal cache until I explicitly call $.remove(). If I use a framework such as React which removes DOM nodes on its own (using native DOM element APIs), how do I clean up jQuery's mem cache?
I'm designing a fairly large app using React. For those unfamiliar, React will tear down the DOM and rebuild as needed based on its own "shadow" DOM representation. The part works great with no memory leaks.
Flash forward, we decided to use a jQuery plugin. After React runs through its render loop and builds the DOM, we initialize the plugin which causes jQuery to hold a reference to the corresponding DOM nodes. Later, the user changes tabs on the page and React removes those DOM elements. Unfortunately, because React doesn't use jQuery's $.remove() method, jQuery maintains the reference to those DOM elements and the garbage collector never clears them.
Is there a way I can tell jQuery to flush its cache, or better yet, to not cache at all? I would love to still be able to leverage jQuery for its plugins and cross-browser goodness.
jQuery keeps track of the events and other kind of data via the internal API jQuery._data() however due to this method is internal, it has no official support.
The internal method have the following signature:
jQuery._data( DOMElement, data)
Thus, for example we are going to retrieve all event handlers attached to an Element (via jQuery):
var allEvents = jQuery._data( document, 'events');
This returns and Object containing the event type as key, and an array of event handlers as the value.
Now if you want to get all event handlers of a specific type, we can write as follow:
var clickHandlers = (jQuery._data(document, 'events') || {}).click;
This returns an Array of the "click" event handlers or undefined if the specified event is not bound to the Element.
And why I speak about this method? Because it allow us tracking down the event delegation and the event listeners attached directly, so that we can find out if an event handler is bound several times to the same Element, resulting in memory leaks.
But if you also want a similar functionality without jQuery, you can achieve it with the method getEventHandlers
Take a look at this useful articles:
getEventHandlers
getEventListeners - chrome
getEventListeners - firebug
Debugging
We are going to write a simple function that prints the event handlers and its namespace (if it was specified)
function writeEventHandlers (dom, event) {
jQuery._data(dom, 'events')[event].forEach(function (item) {
console.info(new Array(40).join("-"));
console.log("%cnamespace: " + item.namespace, "color:orangered");
console.log(item.handler.toString());
});
}
Using this function is quite easy:
writeEventHandlers(window, "resize");
I wrote some utilities that allow us keep tracking of the events bound to DOM Elements
Gist: Get all event handlers of an Element
And if you care about performance, you will find useful the following links:
Leaking Memory in Single Page Apps
Writing Fast, Memory-Efficient JavaScript
JavaScript Memory Profiling
I encourage anybody who reads this post, to pay attention to memory allocation in our code, I learn the performance problems ocurrs because of three important things:
Memory
Memory
And yes, Memory.
Events: good practices
It is a good idea create named functions in order to bind and unbind event handlers from DOM elements.
If you are creating DOM elements dynamically, and for example, adding handlers to some events, you could consider using event delegation instead of keep bounding event listeners directly to each element, that way, a parent of dynamically added elements will handle the event. Also if you are using jQuery, you can namespace the events ;)
//the worse!
$(".my-elements").click(function(){});
//not good, anonymous function can not be unbinded
$(".my-element").on("click", function(){});
//better, named function can be unbinded
$(".my-element").on("click", onClickHandler);
$(".my-element").off("click", onClickHandler);
//delegate! it is bound just one time to a parent element
$("#wrapper").on("click.nsFeature", ".my-elements", onClickMyElement);
//ensure the event handler is not bound several times
$("#wrapper")
.off(".nsFeature1 .nsFeature2") //unbind event handlers by namespace
.on("click.nsFeature1", ".show-popup", onShowPopup)
.on("click.nsFeature2", ".show-tooltip", onShowTooltip);
Circular references
Although circular references are not a problem anymore for those browsers that implement the Mark-and-sweep algorithm in their Garbage Collector, it is not a wise practice using that kind of objects if we are interchanging data, because is not possible (for now) serialize to JSON, but in future releases, it will be possible due to a new algorithm that handles that kind of objects. Let's see an example:
var o1 = {};
o2 = {};
o1.a = o2; // o1 references o2
o2.a = o1; // o2 references o1
//now we try to serialize to JSON
var json = JSON.stringify(o1);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
Now let's try with this other example
var freeman = {
name: "Gordon Freeman",
friends: ["Barney Calhoun"]
};
var david = {
name: "David Rivera",
friends: ["John Carmack"]
};
//we create a circular reference
freeman.friends.push(david); //freeman references david
david.friends.push(freeman); //david references freeman
//now we try to serialize to JSON
var json = JSON.stringify(freeman);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
PD: This article is about Cloning Objects in JavaScript. Also this gist contain demos about cloning objects with circular references: clone.js
Reusing objects
Let's follow some of the programming principles, DRY (Don't Repeat Yourself) and instead of creating new objects with similar functionality, we can abstract them in a fancy way. In this example I will going to reuse an event handler (again with events)
//the usual way
function onShowContainer(e) {
$("#container").show();
}
function onHideContainer(e) {
$("#container").hide();
}
$("#btn1").on("click.btn1", onShowContainer);
$("#btn2").on("click.btn2", onHideContainer);
//the good way, passing data to events
function onToggleContainer(e) {
$("#container").toggle(e.data.show);
}
$("#btn1").on("click.btn1", { show: true }, onToggleContainer);
$("#btn2").on("click.btn2", { show: false }, onToggleContainer);
And there are a lot of ways to improve our code, having an impact on performance, and preventing memory leaks. In this post I spoke mainly about events, but there are other ways that can produce memory leaks. I suggest read the articles posted before.
Happy reading and happy coding!
If your plugin exposes a method to programatically destroy one of its instances (i.e. $(element).plugin('destroy')), you should be calling that in the componentWillUnmount lifecycle of your component.
componentWillUnmount is called right before your component is unmounted from the DOM, it's the right place to clean up all external references / event listeners / dom elements your component might have created during its lifetime.
var MyComponent = React.createClass({
componentDidMount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin();
},
componentWillUnmount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin('destroy');
},
render() {
return <div ref="jqueryPluginContainer" />;
},
});
If your plugin doesn't expose a way to clean up after itself, this article lists a few ways in which you can try to dereference a poorly thought out plugin.
However, if you are creating DOM elements with jQuery from within your React component, then you are doing something seriously wrong: you should almost never need jQuery when working with React, since it already abstracts away all the pain points of working with the DOM.
I'd also be wary of using refs. There are only few use cases where refs are really needed, and those usually involve integration with third-party libraries that manipulate/read from the DOM.
If your component conditionally renders the element affected by your jQuery plugin, you can use callback refs to listen to its mount/unmount events.
The previous code would become:
var MyComponent = React.createClass({
handlePluginContainerLifecycle(component) {
if (component) {
// plugin container mounted
this.pluginContainerNode = React.findDOMNode(component);
$(this.pluginContainerNode).plugin();
} else {
// plugin container unmounted
$(this.pluginContainerNode).plugin('destroy');
}
},
render() {
return (
<div>
{Math.random() > 0.5 &&
// conditionally render the element
<div ref={this.handlePluginContainerLifecycle} />
}
</div>
);
},
});
How about do this when the user exits the tab:
for (x in window) {
delete x;
}
This is much better to do, though:
for (i in $) {
delete i;
}
I'm a fairly experienced knockout user, so I understand quite a bit of the under the hood stuff, I have however been battling now for a few days trying to figure out how to achieve a given scenario.
I have to create a system that allows observable's within a given knockout component to be able to translate themselves to different languages.
to facilitate this, I've created a custom binding, which is applied to a given element in the following way.
<p data-bind="translatedText: {observable: translatedStringFour, translationToken: 'testUiTransFour'}"></p>
This is in turn attached to a property in my knockout component with a simple standard observable
private translatedStringFour: KnockoutObservable<string> = ko.observable<string>("I'm an untranslated string four....");
(YES, I am using typescript for the project, but TS/JS either I can work with.....)
With my custom binding I can still do 'translatedStringFour("foo")' and it will still update in exactly the same way as the normal text binding.
Where storing the translations in the HTML5 localStorage key/value store, and right at the beginning when our app is launched, there is another component that's responsible, for taking a list of translation ID's and requesting the translated strings from our app, based on the users chosen language.
These strings are then stored in localStorage using the translationToken (seen in the binding) as the key.
This means that when the page loads, and our custom bind fires, we can grab the translationToken off the binding, and interrogate localStorage to ask for the value to replace the untranslated string with, the code for our custom binding follows:
ko.bindingHandlers.translatedText = {
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Ask local storage if we have a token by that name
var translatedText = sessionStorage[translationToken];
// Check if our translated text is defined, if it's not then substitute it for a fixed string that will
// be seen in the UI (We should really not change this but this is for dev purposes so we can see whats missing)
if (undefined === translatedText) {
translatedText = "No Translation ID";
}
associatedObservable(translatedText);
ko.utils.setTextContent(element, associatedObservable());
}
}
Now, thus far this works brilliantly, as long as the full cache of translations has been loaded into localStorage, the observables will self translate with the correct strings as needed.
HOWEVER......
Because this translation loader may take more than a few seconds, and the initial page that it's loading on also needs to have some elements translated, the first time the page is loaded it is very possible that the translations the UI is asking for have not yet been loaded into into localStorage, or may be in the process of still loading.
Handling this is not a big deal, I'm performing the load using a promise, so the load takes place, my then clause fires, and I do something like
window.postMessage(...);
or
someElement.dispatchEvent(...);
or even (my favorite)
ko.postbox.publish(...)
The point here is I have no shortage of ways to raise an event/message of some description to notify the page and/or it's components that the translations have finished loading, and you are free to retry requesting them if you so wish.
HERE IN.... Lies my problem.
I need the event/message handler that receives this message to live inside the binding handler, so that the very act of me "binding" using our custom binding, will add the ability for this element to receive this event/message, and be able to retry.
This is not a problem for other pages in the application, because by the time the user has logged in, and all that jazz the translations will have loaded and be safely stored in local storage.
I'm more than happy to use post box (Absolutely awesome job by the way Ryan -- if your reading this.... it's an amazingly useful plugin, and should be built into the core IMHO) but, I intend to wrap this binding in a stand alone class which I'll then just load with requireJs as needed, by those components that need it. I cannot however guarantee that postbox will be loaded before or even at the same instant the binding is loaded.
Every other approach i've tried to get an event listener working in the binding have just gotten ignored, no errors or anything, they just don't fire.
I've tried using the postmessage api, I've tried using a custom event, I've even tried abusing JQuery, and all to no avail.
I've scoured the KO source code, specifically the event binding, and the closest I've come to attaching an event in the init handler is as follows:
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Set up an event handler that will respond to events on session storage, by doing this
// the custom binding will instantly update when a key matching it's translation ID is loaded into the
// local session store
//ko.utils.registerEventHandler(element, 'storage', (event) => {
// console.log("Storage event");
// console.log(event);
//});
ko.utils.registerEventHandler(element, 'customEvent', (event) => {
console.log("HTML5 custom event recieved in the binding handler.");
console.log(event);
});
},
None of this has worked, so folks of the Knockout community.....
How do I add an event handler inside of a custom binding, that I can then trigger from outside that binding, but without depending on anything other than Knockout core and my binding being loaded.
Shawty
Update (About an hour later)
I wanted to add this part, beacuse it's not 100% clear why Regis's answer solves my problem.
Effectively, I was using exactly the same method, BUT (and this is the crucial part) I was targeting the "element" that came in as part of the binding.
This is my mind was the correct approach, as I wanted the event to stick specifically with the element the binding was applied too, as it was said element that I wanted to re-try it's translation once it knew it had the go-ahead.
However, after looking at Regis's code, and comparing it to mine, I noticed he was attaching his event handlers to the "Window" object, and not the "Element".
Following up on this, I too changed my code to use the window object, and everything I'd been attempting started to work.
More's the point, the element specific targeting works too, so I get the actual event, on the actual element, in the actual binding that needs to re-try it's translation.
[EDIT: trying to better answer the question]
I don't really get the whole point of the question, since I don't see how sessionStorage load can be asynchronous.
I supposed therefore sessionStorage is populated from som asynchronous functions like an ajax call to a translation API.
But I don't see what blocks you here, since you already have all the code in your question:
var sessionStorageMock = { // mandatory to mock in code snippets: initially empty
};
var counter = 0;
var attemptTranslation = function() {
setInterval(function() { // let's say it performs some AJAX calls which result is cached in the sessionStorage
var token = "token"; // that should be a collection
sessionStorageMock[token] = "after translation " + (counter++); // we're done, notifying event handlers
window.dispatchEvent(new Event("translation-" + token));
}, 500);
};
ko.bindingHandlers.translated = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = valueAccessor();
var token = val.token;
console.log("init");
window.addEventListener("translation-" + token, function() {
if (token && sessionStorageMock[token]) {
val.observable(sessionStorageMock[token]);
}
});
}
};
var vm = function() {
this.aftertranslation = ko.observable("before translation");
};
ko.applyBindings(new vm());
attemptTranslation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="translated: { observable: aftertranslation, token: 'token' }, text: aftertranslation" />
I'm working on an Single Page Application and we're using Knockout quite extensively. We've currently got a list of item that can be clicked, and upon doing so they'll load some content into a modal container. The image below illustrates the different items that'll trigger various content to be displayed:
The content of these containers differs substantially, and can have many different custom bindings spread over several tabs. The items in the image are fairly simple and just use Knockout Components but when we start displaying the modal contents they are much more heavy on the JavaScript hence using bindings.
I've recently added in lazy loading of the JavaScript and HTML templates required by the components and this has worked really well. I've had to use a custom component loader as for various reasons we don't want to use require or similar AMD module loader.
Now I'm faced with the same issue with custom knockout bindings, as I expect we could end up with 100 hundred bindings quite easily as this product expands. Unfortunately there doesn't seem to be an obvious way to load custom bindings in a lazy way like components though, and I'm trying to figure out if there's a way to do this, and what the best way would be. Note that I also don't know the name of the binding up front all of the time, sometimes I may wish to load them dynamically based on the name of an observable.
The only things I've managed to find of note so far, are that there is a ko.getBindingHandler() function which can be overridden, but it requires a synchronous load of a binding handler.
I have thought of an approach to try and do this, but it uses components and feels like a really backward way of achieving my end goal. It'd be something like this:
Replace a usual custom binding:
<div data-bind="lineChart: $data"/>
with
<div data-bind="component { name: compName, params: { vm: $data } }"/>
I'd then use a custom component loader, which is actually just loading the binding handler JavaScript, and writing out essentially a placeholder div with the custom binding in:
var bindingLoader = {
getConfig: function(name, callback) {
if(name.startsWith("binding-")) {
callback({ binding: name.replace("binding-", ""), jsUrl: "/bindings/" + name });
return;
}
callback(null);
},
loadComponent(name, componentConfig, callback) {
var obj = { };
obj.template = '<div data-bind="' + componentConfig.name + ': $data"/>';
$.ajax({ url: componentConfig.jsUrl, dataType: "text" })
.done(function(data)) {
(new Function(data))();
callback(obj);
});
}
}
I'm sure however there must be a better way of achieving this, but I can't think of any other options right now.
I've also answered this question on Github.
#Jeroen is right that there's no built-in way to asynchronously load custom bindings. But any binding can "lazily" perform its own action, which is what the component binding does. By overwriting ko.getBindingHandler, we can detect bindings that haven't yet been loaded, and start the loading process, then return a wrapper binding handler that applies the binding once it's loaded.
var originalGetBindingHandler = ko.getBindingHandler;
ko.getBindingHandler = function (bindingKey) {
// If the binding handler is already known about then return it now
var handler = originalGetBindingHandler(bindingKey);
if (handler) {
return handler;
}
if (bindingKey.startsWith("dl-")) {
bindingKey = bindingKey.replace("dl-", "");
if (ko.bindingHandlers[bindingKey]) {
return ko.bindingHandlers[bindingKey];
}
// Work out the URL at which the binding handler should be loaded
var url = customBindingUrl(bindingKey);
// Load the binding from the URL
var loading = $.getScript(url);
return ko.bindingHandlers["dl-" + bindingKey] = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
// Once the binding is loaded, apply it to the element
loading.done(function() {
var binding = {};
binding[bindingKey] = valueAccessor;
ko.applyBindingAccessorsToNode(element, binding);
});
// Assumes that all dynamically loaded bindings will want to control descendant bindings
return { controlsDescendantBindings: true };
}
}
}
};
http://jsfiddle.net/mbest/e718a123/
AFAIK: No, there is no generic way to lazily load custom bindings.
There are however a lot of options, but we can not recommend any specific one because they'll heavily depend on context. To summarize a few examples:
If possible you can use those bindings inside components, and lazily load the components;
Depending on what your binding handler does, it can itself delay loading until the latest needed time (e.g. in the init you'll merely register an event callback that will actually load the things you want to load);
If you properly use if bindings, any custom bindings inside of that will not be evaluated until needed. The same for foreach bindings, which will not apply custom bindings for array items unless those items are there.
You can call applyBindings to specific parts of the DOM only when you're ready to do so.
Et cetera. But again, your question borders on being too broad. Create one (or more?) new questions with actual scenario's, tell us why / how you'd need your custom binding to load lazily, and tell us what approaches you've tried and why they didn't work.
Update:
Per my comment, my problem was that I had an extra model that I was passing into the view that I was not unbinding events on. When I saw the event handler being triggered I assumed the source was from this.model instead of this.extra_model, because I had forgotten that this.extra_model was being used for error validations as well.
The solution was to add the following:
MyView = Backbone.extend({
//...
//add method to override BaseView
cleanUp: function() {
this.extra_model.off(null, null, this);
BaseView.prototype.cleanUp.apply(this, arguments);
},
//...
});
Thanks for reviewing the problem, and sorry for the programmer error.
All:
I'm having a problem with stale/zombie events still being bound after I've cleaned up a view. The problem comes when I bind a custom event to a model. When I remove the view from the dom, I call 'this.model.off(null, null, this);' as suggested on various message boards, but although I can see the 'custom-handler' callback getting deleted in chrome debugger tools, I still notice the event handler for 'custom-handler' getting called more times than it should (one extra for every time I recreate the view after cleaning it up) when triggering the event. Could someone tell me if my clean up code is missing something? Thanks in advance!
BaseView = Backbone.extend({
//...
displayErrors:function(){},
cleanUp: function(){
if (this.model) this.model.off(null, null, this);
if (this.collection) this.collection.off(null, null, this);
if (!this.options.persistDataAfterViewCleanup) {
if (this.model) delete this.model;
if (this.collection) delete this.collection;
}
//_.each(this.subViews, function(view){view.cleanUp();}); not needed yet.
this.undelegateEvents();
$(this.el).removeData().unbind();
//Remove view from DOM
this.$el.html('');
this.remove();
}
});
MyView = BaseView.extend({
initialize: function(){
//called manually from model using trigger
this.model.on('custom-handler', this.displayErrors, this);
}
});
Assuming you are on the newest version of Backbone (0.9.10), you should use the new Backbone.Events.listenTo method to bind your events listeners. Using this method Backbone will keep a reference to the object, and clear all event bindings automatically upon view.remove():
this.listenTo(this.model, 'custom-handler', this.displayErrors);
All the things you do in your cleanUp method (delete, undelegateEvents, removeData, unbind, $el.html('')) look a lot like voodoo programming. None of those steps should be necessary at all.
Your zombie views are most likely due to some reference to the view being held by your own code, either directly or indirectly. A reference can be held by an event handler, a bound function, an exported closure, or any number of things. I suggest you try to analyze your code, and use Chrome Developer Tools' Heap profiler tool to try to find the retained objects and their referencers.
Check out my answer in this SO question, where I describe a simple method for finding memory leaks in specific code paths. While your problem is not directly about leaking memory, it's about leaking references, whose retained heap size should help you find what's holding onto them.
You can implement a custom delegateEvents() and undelegateEvents() in a Backbone view.
The Backbone.View constructor calls delegateEvents automatically. I thought that undelegateEvents was called when you remove the view with Backbone.View.prototype.remove, but it is not true.
So, which is the best way to do this manually? I have overridden the remove() view method with this code:
Backbone.View.prototype.remove = function() {
var remove = Backbone.View.prototype.remove;
if (this.undelegateEvents) {
this.undelegateEvents();
}
return remove.apply(this, arguments);
};
It works, but I don't know if is the best option. How should I do this?
As mu is too short suggested, the real answer to the question
which is the best way to do this manually?
is don't. Events are bound to DOM elements, and if those elements go away so do the bindings. undelegateEvents is designed to be used in situations where you aren't removing the DOM element, but still want to take the event bindings off of it (eg. when you set a new element for the view).