How to do proper memory management using YUI to avoid leaks - javascript

We are using YUI's onclick events, but we create and delete dome nodes rapidly and this leads to memory leak.
Consider this example code below, We have many 3 nested divs many times. The top and the bottom div have YUI onclick events attached. What is the proper way to get rid of those dom elements and not leak memory:
I'm really out of any ideas. As you can see I tried implementing our own destroy function. Actually destroy works and does not leak, but it is slow.
The destroy2 function is 'copy' of the YUI destroy function where we used to debug what is the problem. It looks like the recursive clean up of YUI can not find the child nodes in the _instances dictionary
<!DOCTYPE html5>
<html>
<head>
<script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
</head>
<body>
<div id="main">hi there</div>
<script>
YUI().use("node", "event", function(Y) {
window.Y = Y;
function destroy(node) {
(new Y.Node(node)).destroy();
var children = node.children;
for (var i = 0; i<children.length; i++) {
destroy(children[i]);
}
}
function destroy2(node, recursive) {
var UID = Y.config.doc.uniqueID ? 'uniqueID' : '_yuid';
// alert(1);
if (recursive) {
var all = node.all("*");
// alert(all);
Y.NodeList.each(all, function(n) {
instance = Y.Node._instances[n[UID]];
// alert(instance);
if (instance) {
destroy2(instance);
}
});
}
node._node = null;
node._stateProxy = null;
delete Y.Node._instances[node._yuid];
// node.destroy();
}
var main = new Y.Node("#main");
var divs = [];
var iter = 0;
Y.later(10, window, function() {
iter ++ ;
var i;
for (i=0; i<divs.length; i++) {
var d = divs[i];
d.parentNode.removeChild(d);
// (new Y.Node(d)).destroy(true);
//destroy(d);
//destroy2(new Y.Node(d), true);
(new Y.Node(d)).destroy(true);
}
divs = [];
for (i=0; i<1000; i++) {
var d = document.createElement("div");
var i1;
var i2;
d.appendChild(i1=document.createElement("div"));
i1.appendChild(document.createTextNode('inner 1'));
i1.appendChild(i2=document.createElement("div"));
i2.appendChild(document.createTextNode('inner 2'));
Y.on("click", function() {
alert("inner click")
}, i2);
// try to tell YUI to make Node elements
Y.Node.one(d);
Y.Node.one(i1);
Y.Node.one(i2);
// new Y.Node(d);
// new Y.Node(i1);
// new Y.Node(i2);
d.appendChild(document.createTextNode("this is div " + iter + " " + i));
Y.on("click", function(){ alert("you clicked me");}, d);
main.appendChild(d);
//divs.push(i2);
divs.push(d);
}
}, null, true);
})
</script>
</body>
</html>

I'm not sure what you're trying to accomplish here, but a few things stand out in the included code:
var Y = YUI().use(…) -- use() returns the YUI instance. No need to assign window.Y = Y;
Use Y.one(el) instead of new Y.Node(el) or Y.Node.one(el)
Use event delegation rather than subscribing to the click event on each inner div
Use Y.Node.create('<div><div>inner 1<div>inner2</div></div></div>'). You probably don't need Node instances for each div
You're likely creating more work for yourself over the long term by mixing raw DOM interaction and YUI Nodes. If you're using YUI, use YUI's APIs.
The most important of those points are #3 and #4. If you use Node.create (or append, insert, prepend, etc), the markup passed in won't have Nodes created for each element, only the outer most element. If you use event delegation, you won't need individual Nodes, which means you can add your div structures and immediately call node.destroy() (notice not passing true because the inner markup doesn't have nodes needing purging). node.destroy() will purge event listeners, which you don't have because you're using event delegation, and remove the Node from the _instances dictionary to free up memory before any user interaction. If a user clicks on one of the nodes, the event will be caught by the delegate handler and a Node will be created for the e.target element. You're free to call this.destroy() inside your event handler to re-purge the target's Node.
YMMV, considering your code snippet doesn't reflect a real use case. You can also stop into #yui on freenode to get some help or to walk through the issue.

Here is what I ended up doing to avoid the memory leaks:
var destroy = function(dom) {
var ynode = new Y.Node(dom);
ynode.purge(true);
ynode.destroy(true);
}
I have to say I'm very disapointed by the YUI documentation that says this for the YUI destroy function.
With true as a first argument, it works like node.purge(true). The
destroy method does more than detaching event subscribers. Read the
API docs for details.
http://yuilibrary.com/yui/docs/event/
I kind of consider this bug in YUI because it does not call recursively purge when you call destroy recursively. Also it looks like the above destroy function is very slow on Firefox 8 (maybe other versions too) I did write my own recursion down the dom tree and called purge
and destroy without true.

Related

Javascript Nesting Doll Problem - How Dangerous Is It?

I ran into an issue in my current project where I needed to access the array that held an object, as the event listener that an object inside that object needed to access all of the elements of the array. So, I did a little test and discovered for myself that you can, in fact, store the container array that contains an object inside that object and it will actually reference the original array, not a copy.
There are some fairly obvious problems with this in terms of being maintainable code, but I'm curious as to what the potential harm of doing this is. Is there anything I should know regarding this recursive property scenario before I put this in my project? Is there a better way to do what I'm looking to do?
For clarity: the way I'm planning on setting it up is as follows:
The array (linesArray) contains a series of objects of class GraphLine. Each Graphline object (lineObject) contains a Raphael canvas element object (line) for the purposes of formatting that element post drawing. It would also contain a reference property to linesArray (container)
I am planning on having the lineObject.line.mousedown() [which fires on clicking the line] event run a for loop through lineObject.container, aka linesArray, to transform each of the lineObject.line's within it based off of which lineObject.line fired the lineObject.line.mousedown() event.
It would look something like this:
class GraphLine {
id;
line;
lineStr;
container;
constructor(container, /* Bunch of Inputs */) {
this.container = container;
//...
}
Draw() {
this.line = canvas.path(this.lineStr);
this.line.mousedown( function() {
for(let i = 0; i < this.container.length;i i++) {
if(this.container[i].id != this.id) {
//Do A Thing
}
else {
//Do A Different Thing
}
}
});
}
}
var canvas;
$(document).ready(function() {
function Run() {
var container = [];
canvas = new Raphael($('#canvas'), 100, 100);
container.push(new GraphLine(container, /* Bunch of Inputs */));
//...
container[0].Draw();
}
Run();
});

How to find delegated event listeners?

Normally to get listeners on that DOM node I am using
$('selector').data('events');
However this does not show event listeners that are being add via delegation, e.g
$(document).on('click', 'selector', handlerFunction)
One obvious way is to traverse up the DOM tree and look if any of parents are delegating events to element at hand, by concurrently calling $('selector').parent().data('events') until no parent can be found, however this does not strike me as very efficient or standard way of doing things, and I think of it this sort of problem is too common not to have a better solution.
How to find all the event listeners including delegated ones?
ATM I am using functions below, not to elegant - but saves me quite some time.
var getAllEventListeners = function (options) {
if (options.internalArr == undefined)
options.internalArr = [];
if (options.elements.data('events') != undefined) {
options.internalArr.push({
elements: options.elements,
events: options.elements.data('events')
});
}
if (options.elements.parent().length != 0) {
getAllEventListeners({
elements: options.elements.parent(),
internalArr: options.internalArr
});
}
}
var findAllListeners = function (selector) {
var opt = {
elements: $(selector),
internalArr: []
};
getAllEventListeners(opt);
return opt.internalArr;
}

Is it a bad practice to add reference to a Javascript object in one of its attributes?

Let's say I have a setup like this.
var Account = function(data) {
this.data = data;
this.domElement = (function(){ code that generates DOM element that will represent this account })();
this.domElement.objectReference = this;
}
Account.prototype = {
show: function() { this.domElement.classList.remove('hidden'); },
hide: function() { this.domElement.classList.add('hidden') }
}
My question is about the last line: this.domElement.objectReference = this;
It would be a useful thing to have because then I can add event listeners to my DOM element and still get access to the object itself. I can add methods that would affect my DOM element, such as hide() and show(), for instance, without having to resort to modifying visibility of the DOM element using CSS directly.
I tested this code and it works like I want it to, but I'm curious whether this would cause memory leaks or some other unpleasantness or if it's an acceptable thing to do?
Thank you!
Luka
I know this has been answered by #PaulS. already, but I find the answer counter intuitive (returning a DOM element from the Account constructor is not expected) and too DOM-centric, but at the same time the implementation is very simple, so I am not sure what to think ;)
Anyway, I just wanted to show a different way of doing it. You can store Account instances in a map and give them a unique id (perhaps they have one already), then you store that id as a data attribute on the DOM element. Finally you implement a getById function or something similar to retrieve the account instance by id from the listeners.
That's pretty much how jQuery's data works.
Here's an example with delegated events like you wanted from the comments.
DEMO
var Account = (function (accounts, id) {
function Account(data) {
accounts[this._id = ++id] = this;
this.el = createEl.call(this);
}
Account.prototype = {
constructor: Account,
show: function() { this.el.classList.remove('hidden'); },
hide: function() { this.el.classList.add('hidden'); }
};
function createEl() {
var el = this.el = document.createElement('div');
el.className = 'account';
el.innerHTML = el.dataset.accountId = this._id;
return el;
}
Account.getById = function (id) {
return accounts[id];
};
Account.init = function () {
//add delegate listeners
document.addEventListener('click', function (e) {
var target = e.target,
account = Account.getById(target.dataset.accountId);
if (!account) return;
account.hide();
});
};
return Account;
})({}, 0);
//once DOM loaded
Account.init(); //start listening to events
var body = document.body;
body.appendChild(new Account().el);
body.appendChild(new Account().el);
Why not have domElement as a variable, and return it from your function? To keep the reference to your constructed Object (but only where this is as expected), you could do a if (this instanceof Account) domElement.objectReference = this;
You've now saved yourself from circular references and can access both the Node and the Object. Doing it this way around is more helpful if you're expecting to lose the direct reference to your Account instance, but expect to need it when "looking up" the Node it relates to at some later time.
Code as requested
var Account = function (data) {
var domElement; // var it
this.data = data;
domElement = (function(){/* ... */}()); // use var
if (this instanceof Account)
domElement.objectReference = this; // assign `this`
return domElement;
};
// prototype as before
Returned element is now the Node, not the Object; so you'd access the Account instance like this
var domElement = new Account();
domElement.objectReference.show(); // for example
In my opinion there is nothing good about referencing the object inside of the object itself. The main reason for this is complexity and obscurity.
If you would point out how exactly are you using this domElement.objectReference later in the code, I am sure that I or someone else would be able to provide a solution without this reference.

Object to 'hook' into jQuery function, possible?

I am currently running into the following issue, which I'd like to solve more elegantly:
My script works as follows:
Takes an element
Puts the element into a container (defined as var container = $('<div></div>') by using .append()
Keeps track of how far the container is 'filled'
If the container is full, clone a new container and continue there
Repeat this until every element is processed
Right now, this requires me to keep track of a 'fill' (and a 'max') variable to determine how far the container has been filled. So each time I do an append(), I have to increment these counters.
Now, what to me would be more elegant is making the container object smart, and enabling it to 'hook' into the append() event: whenever something is appended, the container object itself executes some code (incrementing its own counter, deciding if it is full, and if it is, returning a new, empty container).
I thought of solving it this way, by creating a function that returns a container:
var container = {
template : $('<div class="container"></div>'),
containers : [],
get : function (i) {
if (!this.containers[i]) {
this.containers[i] = this.template.clone()
.addClass('container-'+i)
.data('max', 500); //this determines the maximum (px) the container can hold
}
return this.containers[i];
}
};
This works, as I can now iterate over all the elements, and call container.get(i).append(element) for each one (while keeping count of height seperately and comparing that to container().get(i).data().max) and later in the script, when I need the output, I can return the container.containers object.
But I can't get it to work having the container.get function to 'watch' for an append() and act on it. I know this is not the way jQuery is meant to work, but I am sure there is another way of doing it, other than keeping local counters in the element iterator.
One other thing I tried is trying to set .on($.append, function() { //do stuff }); on the container, but that was a pipe dream..
I hope I have explained everything clearly, and would love to know if someone has a solution to this.
See this fiddle for a 'working' example (I highly doubt my programming skills)
Maybe you need something like this:
(function($)
{
var oldappend = $.fn.append;
var count = 0;
$.fn.newAppend = function()
{
var ret = oldappend.apply(this, arguments);
//your logic goes here
// count++;
return ret;
};
})(jQuery);
Or you need to store the count variable per container:
(function($)
{
var oldappend = $.fn.append;
$.fn.newAppend = function()
{
var ret = oldappend.apply(this, arguments);
//your logic goes here
if (!this.count){
this.count = 0;
}
this.count++;
return ret;
};
})(jQuery);
Use it:
$('<div class="container"></div>').newAppend(yourElement);

Is it possible to listen for changes to an object's attributes in JavaScript?

I'm working on a fiddly web interface which is mostly built with JavaScript. Its basically one (very) large form with many sections. Each section is built based on options from other parts of the form. Whenever those options change the new values are noted in a "registry" type object and the other sections re-populate accordingly.
Having event listeners on the many form fields is starting to slow things down, and refreshing the whole form for each change would be too heavy/slow for the user.
I'm wondering whether its possible to add listeners to the registry object's attributes rather than the form elements to speed things up a bit? And, if so, could you provide/point me to some sample code?
Further information:
This is a plug-in for jQuery, so any functionality I can build-on from that library would be helpful but not essential.
Our users are using IE6/7, Safari and FF2/3, so if it is possible but only for "modern" browsers I'll have to find a different solution.
As far as I know, there are no events fired on Object attribute changes (edit: except, apparently, for Object.watch).
Why not use event delegation wherever possible? That is, events on the form rather than on individual form elements, capturing events as they bubble up?
For instance (my jQuery is rusty, forgive me for using Prototype instead, but I'm sure you'll be able to adapt it easily):
$(form).observe('change', function(e) {
// To identify changed field, in Proto use e.element()
// but I think in jQuery it's e.target (as it should be)
});
You can also capture input and keyup and paste events if you want it to fire on text fields before they lose focus. My solution for this is usually:
Gecko/Webkit-based browsers: observe input on the form.
Also in Webkit-based browsers: observe keyup and paste events on textareas (they do not fire input on textareas for some reason).
IE: observe keyup and paste on the form
Observe change on the form (this fires on selects).
For keyup and paste events, compare a field's current value against its default (what its value was when the page was loaded) by comparing a text field's value to its defaultValue
Edit: Here's example code I developed for preventing unmodified form submission and the like:
What is the best way to track changes in a form via javascript?
Thanks for the comments guys. I've gone with the following:
var EntriesRegistry = (function(){
var instance = null;
function __constructor() {
var
self = this,
observations = {};
this.set = function(n,v)
{
self[n] = v;
if( observations[n] )
for( var i=0; i < observations[n].length; i++ )
observations[n][i].apply(null, [v, n]);
}
this.get = function(n)
{
return self[n];
}
this.observe = function(n,f)
{
if(observations[n] == undefined)
observations[n] = [];
observations[n].push(f);
}
}
return new function(){
this.getInstance = function(){
if (instance == null)
{
instance = new __constructor();
instance.constructor = null;
}
return instance;
}
}
})();
var entries = EntriesRegistry.getInstance();
var test = function(v){ alert(v); };
entries.set('bob', 'meh');
entries.get('bob');
entries.observe('seth', test);
entries.set('seth', 'dave');
Taking on-board your comments, I'll be using event delegation on the form objects to update the registry and trigger the registered observing methods.
This is working well for me so far... can you guys see any problems with this?
You could attach a listener to a container (the body or the form) and then use the event parameter to react to the change. You get all the listener goodness but only have to attach one for the container instead of one for every element.
$('body').change(function(event){
/* do whatever you want with event.target here */
console.debug(event.target); /* assuming firebug */
});
The event.target holds the element that was clicked on.
SitePoint has a nice explanation here of event delegation:
JavaScript event delegation is a simple technique by which you add a single event handler to a parent element in order to avoid having to add event handlers to multiple child elements.
Mozilla-engined browsers support Object.watch, but I'm not aware of a cross-browser compatible equivalent.
Have you profiled the page with Firebug to get an idea of exactly what's causing the slowness, or is "lots of event handlers" a guess?
Small modification to the previous answer : by moving the observable code to an object, one can make an abstraction out of it and use it to extend other objects with jQuery's extend method.
ObservableProperties = {
events : {},
on : function(type, f)
{
if(!this.events[type]) this.events[type] = [];
this.events[type].push({
action: f,
type: type,
target: this
});
},
trigger : function(type)
{
if (this.events[type]!==undefined)
{
for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
{
this.events[type][e].action(this.events[type][e]);
}
}
},
removeEventListener : function(type, f)
{
if(this.events[type])
{
for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
{
if(this.events[type][e].action == f)
this.events[type].splice(e, 1);
}
}
}
};
Object.freeze(ObservableProperties);
var SomeBusinessObject = function (){
self = $.extend(true,{},ObservableProperties);
self.someAttr = 1000
self.someMethod = function(){
// some code
}
return self;
}
See the fiddle : https://jsfiddle.net/v2mcwpw7/3/
jQuery is just amazing. Although you could take a look to ASP.NET AJAX Preview.
Some features are just .js files, no dependency with .NET. May be you could find usefull the observer pattern implementation.
var o = { foo: "Change this string" };
Sys.Observer.observe(o);
o.add_propertyChanged(function(sender, args) {
var name = args.get_propertyName();
alert("Property '" + name + "' was changed to '" + sender[name] + "'.");
});
o.setValue("foo", "New string value.");
Also, Client Side templates are ready to use for some interesting scenarios.
A final note, this is fully compatible with jQuery (not problem with $)
Links: Home page, Version I currently use
I was searching for the same thing and hitted your question... none of the answers satisfied my needs so I came up with this solution that I would like to share:
var ObservedObject = function(){
this.customAttribute = 0
this.events = {}
// your code...
}
ObservedObject.prototype.changeAttribute = function(v){
this.customAttribute = v
// your code...
this.dispatchEvent('myEvent')
}
ObservedObject.prototype.addEventListener = function(type, f){
if(!this.events[type]) this.events[type] = []
this.events[type].push({
action: f,
type: type,
target: this
})
}
ObservedObject.prototype.dispatchEvent = function(type){
for(var e = 0; e < this.events[type].length; ++e){
this.events[type][e].action(this.events[type][e])
}
}
ObservedObject.prototype.removeEventListener = function(type, f){
if(this.events[type]) {
for(var e = 0; e < this.events[type].length; ++e){
if(this.events[type][e].action == f)
this.events[type].splice(e, 1)
}
}
}
var myObj = new ObservedObject()
myObj.addEventListener('myEvent', function(e){// your code...})
It's a simplification of the DOM Events API and works just fine!
Here is a more complete example

Categories