JavaScript - do local class variables get garbage collected? - javascript

My question is similar to Do local variables inside of a loop get garbage collected?
I have a class that uses recursion to play a blip sound effect in a typewriter style character dialogue screen:
class DialogueBox extends Component{
constructor(props){
super(props)
this.state = {...
}
...
}
typeWriter(txt,i,speed=50) {
if(i==txt.length){
...
return
}
else{
// a blip sound effect plays on every new character typed
let sfx = new Audio(speechBlip);
sfx.play();
...
setTimeout(()=>this.typeWriter(txt,i+1,speed),speed);
}
}
Note the local variable let sfx = new Audio(speechBlip) that is being instantiated multiple times. Will this result in a ton of Audio objects stored in memory that will never be cleaned up?
I'm using this method because I like how it sounds more than creating one Audio() in the constructor and re-setting it to 0 time or only replaying when the file completes playing.
Would this method be a major drag on memory? I tried using the memory panel of dev tools but I am not sure if I am interpreting it correctly and am unsure about how it will scale...

TL;DR; yes it will be cleared up
Answer:
There is a chance it might not be cleared. if you ever store that sfx variable for later use ( lets say you add to some form of event, timeout, etc... ) then it will be cleared after that listener is executed.
This is a very very situational problem tho, keep that in mind! If for example you have an event emitter and you attach a function to an on event, then that function will not be cleared from memory ( for example ).
Anyway. If that variable is used to just do sfx.play(), then it will be cleared from memory.
A small suggestion tho. Why don't you create a class variable lets say: this.blipSound = new Audio(), and use this.blipSound.play() where you need it, you don't have to set it to null or 0 every time as you suggested, just keep it? That way you won't have to worry about memory leaks, as there will be one instance of the class?

Related

Cocos2d-html5 - Restarting/quitting engine possible?

With Cocos2d-html5, is there any way to “quit” the engine and re-initialize its state so you can later restart the engine. We are using a Cocos2d game in a single page web app. If the user navigates to another page, we want to programmatically remove the canvas div and attempt to exit the Cocos2d engine. Unfortunately, it seems there’s no good way to do this and attempting to load a new game causes errors.
How can you cleanly unload a scene and quit the engine?
We are using V3.1 and tried several approaches, none of them seem to work. For example:
Trying to reset many variables that are held in the cc object (detailed below). This approach forces you to reset variables that look private as they start with _ and are not in the documentation.
picking, choosing and rewriting some of CCBoot.js but this appeared complicated and not sustainable for engine updates as so much of it is depended upon throughout the library.
Other approaches I thought of but all sound like a hack:
3. Null the whole cc object and somehow run the script again but that might mean stripping out a script tag or module and adding and running it again.
4. Wrap the cc object in another object so it is easier to reset. CCBoot.js looks like it might attach some things to the window object.
I have got furthest with the first approach but am stuck with context issues. When leaving the canvas, before I remove it from the DOM I call these:
// Remove anything that might possibly keep a reference to the old context
cc.textureCache.removeAllTextures();
cc._drawingUtil = null;
cc.stencilBits = null;
// Purge the cc.director, schedules, event listeners, running scene, animations, cached data.
cc.director.purgeDirector();
// Remove references to the context
cc._renderContext = null;
cc.webglContext = null;
window.gl = null;
cc._mainRenderContextBackup = null;
// Remove reference to DOM elements
cc._canvas = null;
cc.container = null;
cc._gameDiv = null;
// Reset CCBoot variables that might stop us from re-initialising
cc._rendererInitialized = false;
cc._setupCalled = false;
cc._renderType = -1;
Then when we restart on the second or subsequent time call
// Reset all system and flag variables
cc._initSys(cc.game.config, cc.game.CONFIG_KEY);
cc.game.run();
And here are the kind of errors I get. It looks like it's not properly resetting the context:
WebGL: INVALID_OPERATION: bindTexture: object not from this context
WebGL: INVALID_OPERATION: texImage2D: no texture
WebGL: INVALID_OPERATION: uniformMatrix4fv: location is not from current program
WebGL: INVALID_OPERATION: vertexAttribPointer: no bound ARRAY_BUFFER
I've managed to get it stopping/resuming enough for a specific use case now, here is a demo:
http://plnkr.co/edit/BQmmHi?p=preview
There is now a public way to pause a game in v3.13, so I can call:
cc.game.pause();
followed by removing the cc.container from the DOM:
cc.container.parentNode.removeChild(cc.container);
Then I can place it back in the DOM again anywhere I would like:
targetContainerEl.appendChild(cc.container);
Then resume the game by calling:
cc.game.resume();
Note: The next line was was needed after replacing in the DOM in a complex app in cocos2d v3.5, but not sure if it's needed in v3.13 any more:
cc.EGLView._instance._frame = cc.container.parentNode;
The demo from the link above is being removed and placed in 2 different DOM elements and pausing/resuming.

Simple function using a lot of memory

I'm using local storage because after the click, the page reloads, so I can keep track of the last item clicked.
As you can see, I've tried to clear localStorage in order to shrink the memory in use, but it's already at 1.000.000K in less then 10 minutes of usage.
Is this script redeclaring this variables at different location everytime my page reloads?
What is happening that is making it use so mant memory?
This is my entire code.
It's an extension I'm creating for chrome, it selects an option and clicks the button, the button submits a form, the page reload, and it does eveything again and again.
var last = localStorage.getItem('last');
var current = getNext(last);
var prox = getNext(current);
localStorage.clear();
$('select[name="myselect"] option').each(function(){
if($(this).val().indexOf(current)>-1){
$(this).prop('selected', true);
$('.abc').first().click();
localStorage.setItem('last',current);
}
});
function getNext(current){
var arrIds = ['227','228','229','230','231','232'];
return arrIds[arrIds.indexOf(current)+1] || '227';
}
Updated code, without var declarations, that has decreased memory consumption drastically, but with time, the memory raises (in ten minutes went from 160.000K to 240.000K):
$('select[name="myselect"] option').each(function(){
if($(this).val().indexOf(getNext(localStorage.getItem('last')))>-1){
$(this).prop('selected', true);
$('.abc').first().click();
localStorage.setItem('last',getNext(localStorage.getItem('last')));
}
});
function getNext(current){
var arrIds = ['227','228','229','230','231','232'];
return arrIds[arrIds.indexOf(current)+1] || '227';
}
As per the discussion in the comments below the question, the issue appears to come from jQuery itself. The exact reason isn't known at this point, but it appears jQuery has retained data that is not being released when the form is submitted.
This probably has to do in part with some aspect of it being a Chrome extension, since typically on a refresh, the browser would release all memory including globals.
jQuery creates great potential for memory leaks by holding data and closures in a global reference called jQuery.cache. If this is not cleaned up properly, leaks abound.
Since you're creating a Chrome extension, there shouldn't be much to compel you to use jQuery, since you'll not need to worry about browser incompatibility from browsers like IE6 and 7. By using the DOM API directly without such dependencies, the overall code will be smaller and much faster.

Utilizing Firefox's default/built-in Event Listeners

I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined

Ajax Client Control - memory leak

I am creating an Ajax Client Control in ASP.Net. By inheriting from IScriptControl and then adding the relavant javascript class (which would inherit from a javascript control). I have found a memory leak in the following code:
Type.registerNamespace("mynamespace");
myClass = function (element) {
myClass.initializeBase(this, [element]);
}
myClass.prototype = {
initialize: function () {
myClass.callBaseMethod(this, 'initialize');
var me = this;
$(document).ready(function () {
me._initializeControl();
me._hookupEvents();
});
},
dispose: function () {
//Add custom dispose actions here
myClass.callBaseMethod(this, 'dispose');
},
//...other code ...
_hookupEvents: function () {
var me = this;
var e = this.get_element();
$("#viewRates", e).click(function () {
me.openDialog();
});
},
//...other code...
myClass.registerClass('myClass', Sys.UI.Control);
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
_hoookupEvents is a function in my javascript file. The leak is related ot the line me.openDialog. If I remove this line, there is no leak. However, I need this line to be able to call a function from the class (I cannot just use 'this' in the function because it would refer to the button). Is there a better way to do this? Or maybe I just need to call some methods in the dispose function to clean such things?
The memory leak at this code can happen on this line, as you also note
$("#viewRates", e).click(function () {
me.openDialog();
});
when you call it with UpdatePanel, or in general call it for the same component and with out first clear the previous events for the click, the previous handler stay on, and here we have two cases.
To register the same click event more than ones.
To update the dom with ajax, and not previous clear that handlers, as results the previous code stay for ever (for ever == until you leave the page).
In general the solution is to clear any previous handler for the click,
before add a new one.
when initialize a new ajax call with UpdatePanel and before get the new response.
Use a function like that to remove the click and clear the resource for the handler.
this.get_events().removeHandler('click');
I'm extremely hesitant to call it a memory leak if there are only 2 instance of myclass. If there are 2,000 instances of myclass there's DEFINITELY a leak.
I'd search real hard for any dynamic instantiation statements that you have, that create myClass on certain conditions. That is what i see a lot (creating classes in loops at application init, perhaps a form submit can trigger instantiation and it wasn't fully QA'd to see if you can get a submission to create multiple objects, etc).

Questions on use of image.src for statistic

I have a problem that when I was using image.src to make a request on a 1x1 gif. But when I use chrome and safari, the request cannot be logged in my web server. But when I use a javascript to delay for some mini-second(let's say 300ms), it works then. Do any one know a better solution instead of using 300ms delay(as it makes my click become slower)?
My javascript looks like that
/* Event Capture */
function eventCapture(et,ep,eid,eurl) {
var ec=new Image();
ec.src=cp+cd+cu
+"&et="+escape(et)
+"&ep="+escape(ep)
+"&ei="+escape(eid)
+"&eu="+escape(eurl)+_do+_vo
+"&cb="+new Date().getTime();
}
Does anyone know the reqson?
How are you setting the delay? If you just call
setTimeout("javascript function",milliseconds);
on the event capture and put the delay in there, it shouldn't block the rest of your page and will be able to handle multiple clicks at once since each call would call a new setTimeout that is independent from the rest.
As far as your code is concerned, I'm assuming the variables cp, cd, and cu are defined globally elsewhere? If something isn't defined when the script is first called, Chrome will throw a ReferenceError while you're creating the src string of the image, which could be what is throwing everything off.
One possibility is that the image is garbage collected immediately because there's no lasting reference to the image variable ec. But, when you do a setTimeout(), it creates a closure that make the variable last long enough.
You could test this solution that creates a temporary closure only until the image actually loads to see if it solves the problem:
/* Event Capture */
function eventCapture(et,ep,eid,eurl) {
var ec=new Image();
ec.onload = function() {
ec = null;
}
ec.src=cp+cd+cu
+"&et="+escape(et)
+"&ep="+escape(ep)
+"&ei="+escape(eid)
+"&eu="+escape(eurl)+_do+_vo
+"&cb="+new Date().getTime();
}

Categories