I have an object that generates HTML elements that are also connected with an array of the object, and let us say we have one instance of it. So as it creates the elements it also assigns the following event listener to a nested part of the element (the class being uploadDelete).
Now this event listener needs to call the delete method of the instance of the object that created it, with the value of i assigned at its creation. Because events are under Window, the instance needed to be passed to an anonymous function along with the i value.
This therefore assigns a very unique function to the event, and because the delete method will be destroying the element containing the listener I would like to remove it first; from what I've read it could cause leaks otherwise(?). I'm also using Strict Mode, so not arguments.callee.
file.display.getElementsByClassName('uploadDelete')[0].addEventListener('click',
(function(that,i){
return function() {
that.delete(i);
};
})(this,i), false);
I've tried many different things, but when I started having an anonymous function inside of a function inside of a function which is then called in the listener, I figured I should just ask on here. I might have a solution to the overall problem, changing other code, but it would still help if this could be answered.
Here is what I ended up doing, with the help of Norguard's answer. Since the uniqueness was stemming from an object called file, I just created a new property of file to store the function:
file.deleteFunction = (function(that,i){
return function() {
that.delete(i);
};
})(this,i);
file.display.getElementsByClassName('uploadDelete')[0].addEventListener('click',file.deleteFunction, false);
The delete function that is called then removes the event listener.
A relatively-painless way of doing this might be to create an object in the scope that's responsible for adding and deleting listeners, which builds an ID, serial or non, and will store whatever the listener is in an object, with that ID, returning the ID to whichever object/module requested it (or passing the anonymous function back to them).
// trivial example
var listeners = {},
i = 0,
add = function (context, func, closure) {
var enclosed = (function (closure) {
return function () { /* ... */; func(); };
}(closure)),
index = i;
context.addEventListener("...", enclosed, false);
listeners[index] = enclosed;
i += 1;
return index;
};
add will now add your listener, but will also store the function that you're passing into addEventListener into the listeners object.
If you need a reference to i, you've already got it in the closure, if you want it.
So now when you remove stuff, you can just look for the function saved at listeners[i].
An alternate, if you don't want to save a table full of these in one spot, for whatever reason, would be to catch the return statement, and instead of returning i, return the function;
// inside of your module
// I'm not usually crazy about `this`, without doing something particular with it,
// but hopefully it illustrates my point
this.cached_func = add(this.el, this.callback.bind(this), this.secret);
So now, when it comes time to delete everything, and you want to shut down your listener...
remove(this.cached_func);
All of that said, the leaks that you've read about are still possible, but the major culprit was IE6/7 (and earlier).
As people steer further from bad browsers, this becomes less important.
In fact, encouraging memory-dumps in IE6 is probably just a good way to encourage people to not use IE6.
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am an absolute newbie, and I just read this in JavaScript: The Good Parts.
In the chapter talking about scope, it says "It is important to understand that the inner function has access to the actual variables of the outer functions and not copies in order to avoid the following problem." And then the two following examples look like this:
//BAD EXAMPLE
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};
//END BAD EXAMPLE
var add_the_handlers = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};
var i;
for (i = 0; i < nodes.length; i += 1) {
modes[i].onclick = helper(i);
}
};
According to the author the second example is better because it doesn't use a loop inside the function, otherwise it could be wasteful computationally. But I am at loss and don't know what to do with them. How do I put his theory in real application? Can anyone illustrate these two examples combine HTML?
The problem is with closure. The inner functions have access to the variable i defined outside of these functions. After all iterations of the loop have been executed, the variable i will hold the value of nodes.length. So when you click on nodes[0], the alert will say nodes.length, which is not what you'd expect. (You would expect the alert to say 0.) The same holds when you click on nodes[1], nodes[2], etc. The alert for all of them will say nodes.length.
Firstly, in the bad example, a function is created for each event handler; the loop creates multiple function objects. Whereas in the second example, a single function is created and referenced from inside the loop. So you save a lot of memory.
Secondly, in the bad example, as the value of "i" runs, the function does not retain the value, and when it runs, it will always return the last value of "i". In the good example however, as "i" is passed into the function, this value is retained as the lexical environment of the function, and when it is called, it will return the correct value.
Thirdly, as mentioned by #Gary Hayes, we might want to use the function elsewhere too. So it's best to keep it independent of the loop.
You can check it with HTML working here: https://jsfiddle.net/vdrr4519/.
'multifunc' elements are inited with example with many functions, 'singlefunc'—with a single one. See, we take all the elements with a class and pass them to the function.
multifunc(document.querySelectorAll('.multifunc'));
Function runs 'for' loop and adds 'click' event listener. So the element should alert its index on click (beginning from 0). But in example with many function a wrong value is produced (because of closure, other answers also highlight the issue).
I think I should say also that it's not issue of single function/mutliple functions—it's a question of working with closures. You see, I can implement a working example WITH many closures: https://jsfiddle.net/pr7gqtdr/1/. I do basically the same thing that you do in a single-function handler, but every time call the new 'helper' function:
nodes[i].onclick = function (i) {
return function (e) {
alert(i);
};
}(i);
See, this (i) at the end is an immediate function call, so onclick gets a function with i variable set in closure.
But, the single function options is a bit better, because it's more memory efficient, I guess. Functions are objects. If you create many of them, you take more memory, in general. So, choosing from these, I'd stick with 'handler' function option.
The bad example creates a lot of event handlers; One per event. The good example create a single event handler and assigns it to all the events.
With the bad example, you've created lots of separate functions, instead of just one. That can be a lot of extra overhead and a lot of potential scope problems. These include closure issues such as an event only firing for the last item in the loop.
Additionally, the good example allows you to more easily unsubscribe the events because you have access to the original function pointer.
The good example is also just easier to read and understand. The loop is only used for creating the elements and binding their events; The handling of those events is done elsewhere.
As Soviut mentions, you are creating lots of event handlers in the bad example. Moreover, it is important to point out that the bad example functions refer to the same i variable, which means all of them will have the last value of nodes.length when they execute.
This is because a closure is created. You can read more about it in Closures.
I was looking at this basic example below (which makes all images in the DOM semi-transparent on mouseover), and was confused as to how an arbitrary function, such as handleMouseOver, receives an event object if you give it an argument.
How is it that the act of assigning such a function to the onmouseover attribute tells it to modify this function in this way, as there's nothing inherent in the function definition itself that says: "please pass me an event"? Is the assignment operator being overloaded somehow? Or is the browser doing some extra work here? I would really appreciate a link to a detailed explanation of this phenomenon because it doesn't seem to make any sense looking at it as pure JavaScript (to me at least!)
function handleMouseOver(e) {
e.target.style.opacity = 0.5;
}
function handleMouseOut(e) {
e.target.style.opacity = 1;
}
var elements = document.getElementsByTagName("img");
for (var i = 0; i < elements.length; i++) {
elements[i].onmouseover = handleMouseOver;
elements[i].onmouseout = handleMouseOut;
}
Lets break it down by taking one browser's example. IE'S OnMouseOver Event for instance.
In the remarks section it says it passes IHTMLEventObj for ALL events even for the events that don't require it such as Body.OnLoad.
When we go into IHTMLEventObj's detail, we read the following remarks
Although all event properties are available to all event objects, some properties might not have meaningful values during some events
So, Event object is passed regardless; you have to access the object in some specific events and get event-specific properties to get event-related data.
onmouseover, for example, is an event handler. When the event handler needs to be called (in this case when the browser javascript engine decides it) then it will call it be passing it some pre-determined arguments (all good documentation will explain what those arguments are). Your use of those arguments is optional however.
This can be demonstrated with a manual function call like so:
function myFunction(e){
alert(e.myProperty);
}
//assign the handler
var handler = myFunction;
//when required, create event parameter data and call the function assigned to the handler
var myE = { myProperty: "some data" };
handler(myE);
It is not "exactly" how it works (because I don't know how browsers have chosen to implement their code), but it shows the concept.
Here is an example in action
Not only the event object is passed, but also the this value within the function is set to the event target. This is done by the browser, and dictated by the DOM specification.
EDIT:
I was hoping to find something more detailed in the DOM specification (I'm sure I've seen that before), but so far I found this:
In JavaScript, user-defined functions are considered to implement the EventListener interface. Thus the Event object will be provided as the first parameter to the user-defined function when it is invoked. Additionally, JavaScript objects can also implement the EventListener interface when they define a handleEvent method.
https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#glossary-event-handler
By the way, the last sentence is talking about an interesting way to bind event listeners, in an OO context.
You can pass whatever arguments you like to any JavaScript function.
Defining them in the function definition just means you have a named, local variable to access them with.
That is to say:
function foo() {
}
foo("hello");
… won't throw an error.
When a function is treated as an event handler (which is what code provided by the browser will do if you assign a function to the onmouseover property of a DOM node) then the event object will be passed as an argument.
In my web application I have a custom object which I've defined with an object function constructor and applied various shared properties through the constructors prototype.
MyObject = function (Name) {
this.Name = Name;
this.ListItem1 = document.createElement('li');
this.ListItem1.onclick = this.SetActive;
this.ListItem2 = document.createElement('li');
this.ListItem2.onclick = this.SetActive;
}
MyObject.prototype.SetActive = function () {
alert('SetActive: ' + this.Name);
}
Now this is a simplified example, my actual object has many more DOM elements attached to it, and each of those DOM elements have many different event listeners. My object also has many other properties and methods as well. I could also potentially have thousands of instances of this object, so code efficiency is important.
My issue right now is that when a DOM event is triggered, the event handler's 'this' property is set to the actual DOM element, not my object instance.
For example:
var HelloObj = new MyObject('Hello');
HelloObj.SetActive();
//This alerts 'SetActive: Hello'
HelloObj.ListItem1.click();
//This alerts 'SetActive: undefined' because 'this' in SetActive
//becomes ListItem1 and obviously ListItem1.Name is undefined
So how can I set the DOM element's event handlers to a function pointer (not a new function instance for each event handler which would be inefficient when there's a large number of object instances) but still retain the context of the object instance itself in regards to 'this'?
Try out bind() like this:
this.ListItem1.onclick = this.SetActive.bind(this);
//and so on
The solution I've come up with and am using for now is to attach a reference to the object instance to the DOM element and then using a wrapper function for the event handlers to call the desired function through that reference:
MyObject = function (Name) {
this.Name = Name;
this.ListItem1 = document.createElement('li');
this.ListItem1.Context = this;
this.ListItem1.onclick = this.SetActiveHandler;
this.ListItem2 = document.createElement('li');
this.ListItem2.Context = this;
this.ListItem2.onclick = this.SetActiveHandler;
}
MyObject.prototype.SetActiveHandler = function () {
this.Context.SetActive();
}
MyObject.prototype.SetActive = function () {
alert('SetActive: ' + this.Name);
}
This solves all of my problems, although I'm not completely satisfied as there are a few pitfalls. Visually it's just not as pretty and is more convoluted. Programmatically it creates a circular reference which I know is generally frowned upon, although in my situation I don't think it should cause any issues as I'm only worried about modern browsers, and I believe the circular reference is fully contained such that if all of the DOM elements have been removed from the document and I delete my reference to the instance of MyObject itself everything should be fully garbage collected (some feedback on this would be greatly appreciated). And I know it's also considered bad practice to attach custom properties to a DOM element, although I don't know if that's still an issue with modern browsers (again, some feedback would be great). It also breaks the object-oriented flow a little bit having to go though a second function, I would have liked to attach the event handler directly to SetActive().
Does anybody have any other solutions or could shed some light on whether or not I'm actually creating more problems than I'm solving?
I have a bunch of elements on a page, all of whose ID's are stored in an array called ids[].
I have initialized a third-party DOM script for each of these divs that detects when the element has been dragged. The next step is to assign a function to the onDrag event of each element.
For simplicity's sake, I'd simply like to show a popup dialog that states the ID of the element that was dragged. I am iterating through my array as follows:
for (i=0;i<ids.length;i++)
{
document.getElementById(ids[i]).onDrag = function(){alert(ids[i])}
}
This all seems well and good, but toggling the drag event of any of my elements causes a dialog to popup that states the ID of the last element in the array. In other words, it looks like the above function in my iteration is always being evaluated for the last index in my array. I feel like I am missing something very simple here but this issue is driving me nuts.
The thing you've encountered is called closure and it is an essential part of Javascript.
A closure is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
What happens is that the anonymous function assigned to ondrag closes over it's environment including variable i. As a consequence whenever i changes outside of this function, i will contain the new value inside accordingly.
The way you can workaround this behavior in the current context is to create another scope with an additional self-executing function.
for (var i=0; i<ids.length; i++) {
document.getElementById(ids[i]).onDrag = (function(i){ // <- i is a parameter
return function() {
alert(ids[i]); // <- i will be the inner value saved from outside
};
})(i); // <- invoke immidiately with the i from outside
}
You can read more on the topic: Use Cases for JavaScript Closures by #kangax
Do the following changes,
for (...){
SetOnDrag(ids[i]);
}
function SetOnDrag(id)
{
document.getElementById(id).onDrag = function() { alert(id); };
}
I have some working Javascript that manipulates the some DOM elements. The problem is, I don't understand why it works, which is never a good thing. I am trying to learn more about object oriented javascript and javascript best practices, so the organization may seems a little strange.
Basically, I wrap two methods that manipulate the DOM inside a CSContent object. I create an instance of that object, content in $(document).ready and bind some events to the functions in content. However, I am confused as to how these functions can still be called after $(document).ready exits. Doesn't that mean that content has gone out of scope, and its functions are not available? Anyway, here is the code:
function CSContent() {
var tweetTextArea = document.getElementById('cscontent-tweet'),
tweetTextElement = document.getElementById('edit-cscontent-cs-content-tweet'),
charCountElement = document.getElementById('cscontent-tweet-charactercount');
this.toggleTweetTextarea = function () {
$(tweetTextArea).slideToggle();
};
this.updateTweetCharacterCount = function () {
var numOfCharsLeft = 140 - tweetTextElement.value.length;
if (numOfCharsLeft < 0) {
$(charCountElement).addClass('cscontent-negative-chars-left');
}
else {
$(charCountElement).removeClass('cscontent-negative-chars-left');
}
charCountElement.innerHTML = '' + numOfCharsLeft + ' characters left.';
};
}
$(document).ready(function () {
var content = new CSContent();
//If the twitter box starts out unchecked, then hide the text area
if ($('#edit-cscontent-cs-content-twitter:checked').val() === undefined) {
$('#cscontent-tweet').hide();
}
$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
//Seems wasteful, but we bind to keyup and keypress to fix some weird miscounting behavior when deleting characters.
$('#edit-cscontent-cs-content-tweet').keypress(content.updateTweetCharacterCount);
$('#edit-cscontent-cs-content-tweet').keyup(content.updateTweetCharacterCount);
content.updateTweetCharacterCount();
});
This, m'lord, is called a closure: the local variable content will remain in memory after $(document).ready exits. This is also a known cause of memory leaks.
In short, you bind this function to an event listener of a DOM element and then the JavaScript garbage collector knows that it should keep the local variable intact. You can't call it directly (outside of the function), unless the event is triggered. With some, you can do this ‘manually’, if you really want to call the function afterward (e.g., using element.click() to simulate a click).
I assume you wonder why the event handlers like
$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
work?
Well you don't pass content as event handler but the function that is contained in content.toggleTweetTextarea. And this reference will still exist after content does not exist anymore. There is nothing special about it. You just assigned an object (the function) to another variable. As long as at least one reference to an object exists, the object won't be garbage collected.
Now you may ask why those functions have still access to e.g. tweetTextArea ? This is indeed a closure. When the functions are created via new CSContent(), the activation context of this function is added to the scope chain of the inner functions CSContent.toggleTweetTextarea and CSContent.updateTweetCharacterCount. So even if you don't have a reference to content anymore, the scope of this function is still contained in the scope chain of the other functions.
You won't be able to access the object contained in content anymore after ready() is finished, this indeed goes out of scope.
My brain is off today, but shouldn't you be using closures in this situation?
$('#edit-cscontent-cs-content-twitter').change(
function(){
content.toggleTweetTextarea();
}
);