Is there anyone who knows how to destroy a javascript (jquery) function?
I'm using jquery "selectable" and a function call "edit" is fired on selectable "stop" event.
Inside this "edit" function I have nested switch functions with a lot of "click" events
and I have many functions within each "click" event. My problem is,
every time I fire the "selectable" functions and events inside the function "edit" is fired again but the previous functions and events still exist.
What i do now is to unbind every event in the function "edit" on selectable "start" even.
Is this a memory leak problem?
and is there a way to "destroy" functions in javascript?
i have tried to declare the function to null when the function ends but this does not work. functions and events inside it still exist.
anyone have a clue?
demo page here -->
http://dreamerscorp.com/test/test01/javascript_destory_test.html
edit 2009/10/31
:) thanks a lot for your helps, your comments are very useful to me, thanks again!!!
You can try to nullify the function, or override it assigning an anonymous function that does nothing:
myFunction = null;
// or
myFunction = function () {};
You can also do it within the function itself:
var autoDestroy = function () {
autoDestroy = null;
//...
return 1;
};
autoDestroy(); // returns 1
autoDestroy(); // TypeError: autoDestroy is not a function
to unbind events in jquery use unbind function http://docs.jquery.com/Events/unbind
$("something").click(function() {
$("anotherOne").click(function() {
....
});
});
in this example, every time "something" is clicked, an event handler is added to "anotherOne", so if you click three times, you'll get three event handlers.
$("something").click(function() {
$("anotherOne").unbind('click').click(function() {
....
});
});
here you're guaranteed to have only one click handler on "anotherOne".
There's no need to destroy previous handler explicitly.
Basically you need to remove all references to those functions so that the JavaScript garbage collector can collect them. If they are bound, you need to unbind them. If there are other variables in there that point to them, they need to be set to null.
It might help if you posted some code; then we can give a better answer.
...EDIT:
What's happening here is, you're creating a closure that will outlive the containing function:
function edit(){
$('.edit').click(function(){
//...
function _edit(boxTitle,selectedItemAmount){
//...
$('#box .yes').click(function(){
alert(boxTitle + ' for ' + selectedItemAmount + ' selected item');
$('#msg').hide(); // hide msg box when yes btn is clicked
});
}
//...
$('#box .no').click(function(){
$('#msg').hide();
});
});
In other words, inside a function, you're saying, "Attach this function to a DOM object," and you're doing it inline. JavaScript captures variables from outer contexts and keeps them alive while the reference to the inner context is alive.
What you need to do is to define the functions somewhere not inline and then use them:
function boxClickYes(e) {
alert(e.data.boxTitle + ' for ' + e.data.selectedItemAmount +
' selected item');
$('#msg').hide(); // hide msg box when yes btn is clicked
}
function boxClickNo(e) {
$('#msg').hide();
}
function edit(){
$('.edit').click(function(){
//...
function _edit(boxTitle,selectedItemAmount){
//...
$('#box .yes').bind("click", {boxTitle: boxTitle,
selectedItemAmount: selectedItemAmount}, boxClickYes);
}
//...
$('#box .no').click(boxClickNo);
});
This also demonstrates how to use the data property in jQuery click handlers to store data in between the time you attach the handler and the time you use it (instead of storing that data in a closure that will keep the scope chain in memory). Using inline-defined functions is fine when you're just using it right there (like the body of a $.each, for instance) but it's not OK when you're attaching event handlers.
To "destroy" a function in javascript, simply ensure that the function becomes unreachable. This will enable the function to be eligible for reclamation. One thing to watch out for is that javascript variables are bound based on scopes (not as individual variables) and a scope, with many unused objects, may persist if a binding is kept to the scope: to provide any more help requires knowledge of the specific code and how it is used.
Please see the javascript delete operator as one way of removing a variable or object member. Setting the value to null/undefined/other-object removes on method of reaching the object previously referenced (although it might still be reachable otherwise and thus not be reclaimed) but does not get rid of the variable/member.
delete variable
delete obj.member
delete obj[member]
Related
I have a search input field to receive user input and a variable-state button that responds to user input. Notice the on-input event on the search input.
<dom-module id="app-search">
<template>
<input type="search" id="searchInput" on-input="_onInput" />
<!-- NOTE: button is set to 'disabled' when there is no value of search field -->
<paper-button disabled>Done</paper-button>
</template>
</dom-module>
In the Polymer ready() definition I get a handle to the paper-button element (The ready() function is called after everything, including after the element and its properties have been initialized, so it's a good time to query the local DOM).
ready() {
super.ready(); // must call this for Polymer to work
// get handle to paper-button element
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
}
(By the way, I know using this.$ can be used as a syntax short-cut for Polymer.dom(this.root).querySelector() but that syntax only seems to work for targeting elements in the local DOM that have an id, e.g: this.$.searchInput will return a handle to the element with id="searchInput". Does anyone know of a shorthand for targeting non-id, regular elements without having to type Polymer.dom(this.root)...?)
I have a function that detects input events on the search field. If there is a value in the search field, enable the button.
_onInput() {
// if search input has value
if (Boolean(this.$.searchInput.value)) {
// remove disabled attr from button
this._doneBtn.removeAttribute('disabled');
} else {
this._disableBtn();
}
}
_disableBtn() {
this._doneBtn.setAttribute('disabled', true);
}
Up to now, this works so that when a user starts typing, the button becomes enabled; when there is no value the button becomes disabled.
However, by convention, users can also delete the input value by clicking the little 'x' that appears on the right-hand-side of the search input. Developers can detect that event by attaching a search event to the input element:
ready() {
super.ready(); // must call this for Polymer to work
// get handle to paper-button element
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
// attach 'search' event listener to search input field
// call the element's '_disableBtn' function
this.$.searchInput.addEventListener('search', this._disableBtn);
}
The problem is when I trigger the event by clicking the 'x' that appears when the search field has a value, the this._disableBtn function fires, but this._doneBtn inside the function returns undefined:
Uncaught TypeError: Cannot read property 'setAttribute' of undefined.
Assuming this might have to do with an improper type definition, I tried declaring the _doneBtn property in the Polymer properties getter:
static get properties() {
return {
_doneBtn: Object // also tried 'String'
}
}
I also tried querying the DOM from inside the _disabledBtn function again and trying to re-declare the property but I still get the same error:
_disableBtn() {
if (!this._doneBtn) {
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
}
this._doneBtn.setAttribute('disabled', true);
}
Can anyone understand what's happening here? This seems to have something to do with the event listener. Perhaps the DOM is not fully rendered before it's parsed although switching the order of the declarations in the ready() call doesn't make a difference? It could also have something to do with this.
Interestingly, when I console.log(this) inside _disableBtn, the console returns two different this instances, one for the host element (<app-search>) and one for the target element that fired the event: two elements point to 'this'. Also noteworthy is the order that this is printed.
I'm hoping someone wiser than me can help solve what's going on here.
My gut feeling is that you don't use the "this" correctly. The "this" expresion is counter-intuitive as we tend to automatically associate it with the lexical scope or the location where the "this" is used. Alas, this is not the case as the "this" binding is not determine at JIT "compile time" nor a "runtime" but at "call time".
The this represents the context it was called from. So by simply moving a line of code from one function to another, the "this" could refer to something else. And that something else may not have a "setAttribute" function attached thus the "of undefined" error message.
One way to workaround this (no pun intended) reality is to configure the called function with a .bind([value we want "this" to have when calling that function]).
For instance, instead of writing the following snippet:
} else {
this._disableBtn();
}
you would have writen
} else {
this._disableBtn().bind(this);
}
You would have made shure that the "this" you use in the _disableBtn() function woud point to the same target as the "this" does in the "else" block.
Basically, the "this" value depends on the original call site context the function it is used in was called (I'm not sure if this is clear... but reread it slowly if it doesn't make sense on the first flight). To hardcode a value of "this" in the called function, you can add the .bind("this" value you want to pass to the called function) when calling the function such as:
function aFunction() {
myfunction(parameter).bind("required value of 'this' in myFunction").
}
Hope this helps.
Jake
After reading #softjake's response, I was able to solve the problem.
First, let's revisit the addEventListener() setup that was added in the ready() function:
ready() {
super.ready();
// handle to toggle button element
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
// previous method === no worky
//this.$.searchInput.addEventListener('search', this._disableBtn);
// changed it to:
this.$.searchInput.addEventListener('search', this._disableBtn.bind(this._doneBtn));
}
The key here is that I'm using .bind(this._doneBtn) to make sure this, inside the scope of the _disableBtn function refers to this._doneBtn and not some other this (like the parent element or document/window).
Finally, adjust the _disableBtn function slightly:
_disableBtn() {
// previous (also no worky)
//this._doneBtn.setAttribute('disabled', true);
// changed to:
this.setAttribute('disabled', true);
}
Because this already refers to this._doneBtn, we can simply use this.setAttribute(...).
EDIT: I've made a few changes to re-clarify the issue I'm having.
I have a simple Javascript plugin that I want to add some callback features to as part of its default functionality. Users would be able to specify a function to be called when a certain button is clicked on.
// Define option defaults
var defaults = {
//some other defaults
onOK: null, //callback functions to be specified here (exists as string)
onClose: null //callback functions to be specified here (exists as string)
}
Normally, this works fine when there's a simple function with no arguments. The option is passed to here:
function initializeEvents() {
//If a close button event is clicked, run this.
if (this.closeButton) {
this.closeButton.addEventListener('click', this.close.bind(this)); //Bind close event to the button
this.closeButton.addEventListener('click', window[this.options.onClose]); //passed to here
}
//If an OK button event is clicked, run this.
if (this.okButton) {
this.okButton.addEventListener('click', this.close.bind(this));
this.okButton.addEventListener('click', window[this.options.onOK]); //passed to here
}
}
window[] is supposed to become a function call to the appropriate button. It has come to my attention that I should NOT be using window[].
EDIT: However, as #adeneo pointed out, I'm making a BIG mistake here by adding my functions as strings, which simply results in the function becoming undefined instead when I have to add it to an event listener.
I've tried using an anonymous function, but I'm still not getting the function to work:
var callbackModal = new raModal({
content: '<div>You clicked on the Callback button. This shows the popup, but has a custom callback function, called when the user clicks OK and/or Close.</div>',
onOK:{function(){
okCallback('4');
}
},
onClose: "closeCallback",
closeButtonText: "Close"
});
callbackModal.open();
So, my question has changed to:
How can I properly add my anonymous functions from the defaults?
Link to a JSFiddle that reproduces the issue. You'll notice that while the Close button works as intended (with closeCallback printing the console.log message), the OK button calling okCallback(num) does nothing.
Any help is greatly appreciated.
You can pass arguments to JavaScript functions without having a parameter name on the function's signature. This should be a workaround for your problem.
See Is it possible to get all arguments of a function as single object inside that function?
Instead of trying to bind the callbacks to the button in initializeEvents() using the risky window[] implementation, it's much easier to simply append them after the button object after they're constructed.
I made two additional functions to add the event listeners for the OK and Close buttons:
function closeCallback(callback){
$(".ra-close").click(callback);
}
function okCallback(callback){
$(".ra-ok").click(callback);
}
And inside the buildOut() function, after everything is done, add the calls to the event listeners, using our custom option as the argument, which can be an anonymous function now!
closeCallback(this.options.onClose);
okCallback(this.options.onOK);
There was an additional utility function that extended user options to the new modal, but didn't track null values properly. I removed an if statement that checked for those, seeing as we had them as part of the defaults variable.
// Utility method to extend defaults with user options
function extendDefaults(source, properties) {
var property;
for (property in properties) {
source[property] = properties[property];
}
return source;
}
Here's the updated JSFiddle with the corrected plugin code.
I'm trying to write some Javascript that when a div is clicked, a function is called with the parameter of that divs ID, the function works when I just send the hardcoded div id like so:
$('#areaOne').on('show.bs.collapse', onSectionClick('#areaOne'));
function onSectionClick(){
var x = $('#areaOne).hasClass('toggled') ? 'false' : 'true';
console.log(x)
}
However when I try it like this:
$('#areaOne').on('show.bs.collapse', onSectionClick('#areaOne'));
function onSectionClick(secID){
var x = $(secID).hasClass('toggled') ? 'false' : 'true';
console.log(x)
}
Then the function is called as soon as the page is loaded, rather then when the area is clicked. I want it to work this way as there are many areas that should trigger the same function.
I'm pretty new to Javascript so any help, or advice on how to do this in a better way would be greatly appreciated.
The problem is that you call the function instead of giving a function reference. Try:
$('#areaOne').on('show.bs.collapse', function(){onSectionClick('#areaOne')});
THe line
$('#areaOne').on('show.bs.collapse', onSectionClick('#areaOne'));
calls onSectionClick, passing in '#areaOne', and passes its return avlue into on, exactly the way foo(bar()) calls bar and passes its return value into foo.
If you want to set it up to be called by the event, you pass a function reference rather than calling the function, e.g.:
$('#areaOne').on('show.bs.collapse', function() { onSectionClick('#areaOne'); });
In your case, though, you probably don't even want to do that. Instead:
$('#areaOne').on('show.bs.collapse', onSectionClick);
// Note no () -------------------------------------^
...and in onSectionClick, use this.id to get the ID of the clicked element ('areaOne' — add the # if you need it).
And if you have other "areas" that you also want to hook up, you can hook them up all at once:
$('selector-for-all-the-areas').on('show.bs.collapse', onSectionClick);
...and then you know which one relates to the event by which element this refers to in onSectionClick.
Let's imagine that we have a code like this:
function someName(callback) {
var elem = document.createElement('input');
elem.addEventListener('change', function(evt) {
callback();
}, false);
// some code
elem.click();
}
// some code
someName(function() {
alert("Hello world!");
});
The question is: will JS completely remove the "elem" element created in the "someName" function after moving out of it's callback's context? The other question is: will JS remove the "elem" element if it is not changed after emitting the "click" event?
Can you, please, explain me when the "elem" element will be removed?
P.S.: I'm trying to write a simple application with node-webkit and such kind of code is needed to let node-webkit open a file dialog (open file, save file and so on) and handle it's result.
Variables defined inside a function only exist inside the function when the function is executed.
Your code creates an input element and it assigns a callback function to it's state change handler, but you don't actually attach/insert the element to the DOM anywhere, so the element only exists as that variable; it never becomes something in the DOM to exist after the function ends
Therefore when the function ends, the variable will be destroyed and that element wouldn't exist anymore, including the state change handler.
sidenote: it's .createElement() not .create() (unless you have code that defined a .create() method..)
I'm trying to register on +1 clicks from within my module, which is wrapped as an annonymous function.
For this end, I created a global object MyModule, and exported my click handler function through it. The problem is - my click handler doesn't get called.
Live demo. Code:
// Initialize +1 button
gapi.plusone.go();
(function(){
window.MyModule = {};
function plusOneClicked() {
alert("+1!");
}
window.MyModule.plusOneClicked = plusOneClicked;
})()
...
<g:plusone callback='window.MyModule.plusOneClicked'></g:plusone>
When I give as a callback an external function, whose only purpose is to forward the calls to window.MyModule.plusOneClicked, it works:
function foo() {
window.MyModule.plusOneClicked();
}
...
<g:plusone callback='foo'></g:plusone>
Why would the click handler miss window.MyModule.plusOneClicked(), but find foo()?
Google is probably writing
window[callback]();
in their code.
This requires that the callback string refer to a property of window, not a property of another object.
I believe because callback expects a direct handler method (as in foo()) rather than a reference (as in window.MyModule.plusOneClicked). So basically, you cannot simply assign such a reference to click handler, but write a (wrapper) method as the handler and have it perform the necessary invocation.