Using this keyword within Socket.io on function - javascript

I'm using a socket.io listener within one of my functions to listen for a "loser" event to tell the client that the other client won. However, I can't use the "this" keyword to talk about my client while inside the socket.on function as this refers to the socket itself. Am I going about this the wrong way? Or can access the client object some other way, like super?
socket.on('loser', function() {
//Remove all current objects then restart the game.
//THIS PART DOESN'T WORK, SINCE 'THIS' NO LONGER REFERS TO
//THE GAME OBJECT, BUT INSTEAD REFERENCES THE SOCKET LISTENER.
for(var i = 0; i < this.board.objects.length; i++)
{
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType,
"Loser!",
"Press Space to Play Again",
playGame));
});

Functions don't carry any information about the objects that reference them, you can use .bind() to bind the function to your object before you pass it:
socket.on('loser', function() {
//Remove all current objects then restart the game.
//THIS PART DOESN'T WORK, SINCE 'THIS' NO LONGER REFERS TO
//THE GAME OBJECT, BUT INSTEAD REFERENCES THE SOCKET LISTENER.
for (var i = 0; i < this.board.objects.length; i++) {
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType, "Loser!", "Press Space to Play Again",
playGame));
}.bind(this));

In browser-land the common way to do this is to set a variable like var that = this; before you enter the function, and then use that instead.
However, ECMAScript5 brought in bind(), which allows you to prevent the value of this being lost. In NodeJS of course, it's safe to use this (unlike in browser-land, where you have to support older browsers).
socket.on('loser', (function() {
//Remove all current objects then restart the game.
for (var i = 0; i < this.board.objects.length; i++) {
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType, "Loser!", "Press Space to Play Again", playGame));
}).bind(this));​
For more info, see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind

Whats wrong with something like this?
var self = this;
socket.on('loser', (function() {
//Remove all current objects then restart the game.
for (var i = 0; i < self.board.objects.length; i++) {
self.board.remove(self.board.objects[i]);
}
}

Related

Qualtrics API functions not working within custom function called in EventListener

I defined a custom function in the header section that checks, alerts the user, and resets the value of a particular slider bar when it fails certain restrictions.
This function works beautifully when called on question clicks:
this.questionclick = chkVals;
I would like to also run the function when the user are exiting the text input field (as some users are using the keyboard to do the survey). I implemented an Event Listener for each sliders' text input field that runs the function when the focus is out of the text input field.
// choices is an array of choice ids
for (i = 0; i < choices.length; i++) {
var x = document.getElementById(choices[i]);
x.addEventListener("blur", chkVals, true);
};
I know that the event listener works, because the correct alerts are popping up. It is just not able to reset the values as this.setChoiceValue is not a function within the environment. I have tried setting var that = this; and calling that.setChoiceValue in the function, but it still does not work.
Any help will be greatly appreciated!
You haven't shown all your code, so I'm making some assumptions.
this is the Qualtrics question object in the addOnload function. Since chkVals is outside the addOnload function, this (or that) is undefined. So, you need to pass it in your function call (function chkVals(qobj)) then use qobj.setChoiceValue in the chkVals function. Then your function calls become:
this.questionClick = chkVals(this);
and
x.addEventListener("blur", chkVals(this), true);
#T. Gibbons 's answer helped me get to this point. As suggested I needed to add a parameter to chkVals() to be able to reference the this object. However,
this.questionClick = chkVals(this);
does not work due to this being a reserved object, so the whole header script will not run. I ended up changing all reference of this to that in my custom function and adding the parameter that as suggested:
function chkVals(that) {
...
... that.setChoiceValue(x, y)
}
To call the function with a parameter, I had to explicitly defined an anonymous function that called chkVals, otherwise it will not work (I am not sure why):
var that = this;
this.questionclick = function() {chkVals(that);}
for (i = 0; i < choices.length; i++) {
var x = document.getElementById(choices[i]);
x.addEventListener("blur", function() {chkVals(that);}, true);
};
The above works!

javascript garbage collector not collecting setTimeout()s?

I have a function:
function test()
{
for( var i = 0; i < 1000000; i++ )
{
setTimeout( function()
{
//
}, 10000 );
}
}
Running this on chrome, it propels memory usage from around 50MB to 600MB, which I guess is ok; but after the timeouts have been executed, the garbage collector doesn't seem to remove them from memory, and it just stays at 600MB until I refresh, even then though it leaves some sort of "foot print" of 150MB after the page refresh.
Any idea how to tell the garbage collector to get rid of them after their execution?
You are correct that there appears to be memory that is never cleaned up. My best guess at the problem is that creating a function in a for loop like this creates a new scope which must have access to i. Therefore, those functions never get cleaned. I misspoke - the functions should definitely get cleaned up. Testing in my browser took the memory up over 900MB.
It's important to note that there is no benefit to doing what you are doing and it can be classified as "poor code" at best. You should create one function and reuse it:
// reusing a function fixes the problem
function test () {
var fn = function () {};
for (var i = 0; i < 1000000; i++) {
setTimeout(fn, 1000);
}
}
My observation was that memory usage shot back up over 900MB and then gradually fell back down to closer to normal over the course of a couple minutes.
If you are needing access to the variable i inside of your functions, I hate to say that your code will not give you that. You will only ever see the last value of i (1000000). If you are wanting to use i inside your functions, you can use a factory function. In my tests the memory eventually got cleaned up:
// using a factory fixes the problem and give you access to 'i'
function test () {
function factory (n) {
return function () {
/* This will get called later. 'n' will represent
the value of 'i' at the time this function was created */
}
}
for (var i = 0; i < 1000000; i++) {
setTimeout(factory(i), 1000);
}
};
Unfortunately, the memory problem persists if you use bind:
// Memory problem still exists with .bind
function test () {
var fn = function (n) { };
for (var i = 0; i < 1000000; i++) {
setTimeout(fn.bind(null, i), 1000);
}
}
As for the memory usage remaining high after a page refresh, this I cannot explain but it might have to do with the browser setting aside more memory for a tab which appears to be using a lot of memory. I dunno, just a guess.

Javascript: Can't handle events for multiple instances

I have put together a fun API for game creation. In the code I create a prototype for Mover and then extend it with several specific prototypes (Gold, Monster, and Hero). Each one is based on a img tag with a given ID. I use type-specific information in the constructor and a single template method for each type. Most of the functional code in Mover depends on those type-specific details. I have included one example for simplicity.
I use method calls in a separate script to create and destroy instances of the Mover child types. When I create and destroy one instance at a time everything works as intended. The image updates, the sound plays and it is removed after the correct delay. If I create two or more, however, only the last one works as expected. So if I make gold, moster, hero. Only the hero will remove correctly. The other two will play the audio, but don't appear to update.
I ran into the same problem when I tried to attach a function to the onclick event for more than one instance. Only the last one worked and the others did nothing. Obviously I'm missing something about the way java handles method assignments. Any explanation you can offer would help.
Thanks,
BSD
function Mover()
{
}
Mover.prototype.InitTag = function()
{
this.HTMLtag.src=this.imageURL;
this.HTMLtag.style.position="absolute";
this.HTMLtag.style.width=characterSize;
this.HTMLtag.style.height=characterSize;
this.Position(Math.floor(Math.random()*(MaxW-characterSize)+(characterSize/2)),Math.floor(Math.random()*(MaxH-characterSize)+(characterSize/2)));
}
Mover.prototype.Destroy = function()
{
var disp = this.HTMLtag.display;
this.HTMLtag.src=this.destroyURL
this.HTMLtag.display = disp;
this.destroyAudio.play();
this.RemoveTag();
}
function Monster(id)
{
this.MonsterID = id;
this.HTMLtag = document.getElementById("monster"+id);
this.imageURL = "monster1.jpg";
this.destroyURL = "monster2.jpg";
this.destroyAudio = monsterAudio;
}
Monster.prototype = new Mover();
Monster.prototype.RemoveTag = function()
{
var mID = this.MonsterID;
setTimeout(function() {field.DeleteMonster(mID)}, 1000);
}
function Hero()
{
this.HTMLtag = document.getElementById("hero");
this.imageURL = "hero1.jpg";
this.destroyURL = "hero2.jpg";
this.destroyAudio = heroAudio;
}
Hero.prototype = new Mover();
Hero.prototype.RemoveTag = function()
{
setTimeout(function() {field.DeleteHero()}, 5000);
}
function Gold(id)
{
this.GoldID = id;
this.HTMLtag = document.getElementById("gold"+id);
this.imageURL = "gold1.jpg";
this.destroyURL = "gold2.jpg";
this.destroyAudio = goldAudio;
}
Gold.prototype = new Mover();
Gold.prototype.RemoveTag = function()
{
var mID = this.GoldID;
setTimeout(function() {field.DeleteGold(mID)}, 1000);
}
---------UPDATE UPDATE UPDATE-----------
I have at least partially fixed the problem. I have gotten it to work, but I still don't know why it didn't function as intended. I noticed that while my browser's (Chrome) developer tools could visually identify the most-recently-added Mover when it was being destroyed, it could not do so with the any other movers.
Tag of most recently added Mover can be identified in Chrome developer tools.
This suggested that Mover.HTMLtag was not actually the same as document.getElementById('mover1'). I was able to confirm this by looking at the variables in the GoldField.DeleteMover. At the line indicated mover.src has not changed, but movers[id].HTMLtag.src has been correctly updated. In the most-recently-added case they were both the same.
GoldField.prototype.DeleteMover = function(id)
{
var isHero = false;
if(this.Hero!=null && id==this.Hero.myID)
{
this.Hero = null;
isHero = true;
}
else if(this.Tower!=null && id==this.Tower.myID)
{
this.Tower = null;
}
var mover = document.getElementById("mover"+id);
if(!isHero)
{
this.tag.removeChild(mover);//<<< HERE HERE HERE HERE
delete this.movers[id];
}
}
So, I changed one line in Mover.Destroy. By finding the tag by ID and setting the src. I was able to reliable behavior. It would appear that Mover.HTMLtag is not reliable the same after the second Mover is added. Any explanation?
Mover.prototype.Destroy = function()
{
document.getElementById(this.HTMLtag.id).src=this.destroyURL;
this.HTMLtag.src=this.destroyURL;//old method
this.destroyAudio.play();
this.RemoveTag();
}
On suspicion that this might extend to other updates to this.HTMLtag I set up some basic movement of the Hero. It works great, but if you add one additional Mover of any kind it no longer moves. That narrows down the question considerably. Why would constructing a second Mover cause the prototype members to change?
So I debug your code and I found the cause of your problem. The problem was when you create a new instance of monster you storing a reference to it on the monster var. And when you delete it you don't delete / update the reference to it. So your delete function myField.DeleteMover(id) try to delete a monster already deleted. How to solve this.
// create an array to keep ref to our instances
var monsters= [];
// var monster = null;
function addMonster()
{
// monster = goldField.AddMonster();⏎
// push every monster in array
monsters.push(goldField.AddMonster());
}
function killMonster()
{
// if array.length is true
if (monsters.length) {
// call the destroy function on the last ref
monsters[monsters.length - 1].Destroy();
// remove the last ref from array using pop
monsters.pop();
}
//monster.Destroy();
}
This is working however I think all of this should be done in the objects itself. And you should not care about it here.
Another advice try to use more array methods. Avoid using delete on array index because it mess with index and count instead use splice(index, 1) same for add item in array use push instead of arbitrary index.
Anyway funny game! Good luck to finish it.
Edit, after your answer I go back an test.
To make it work I do this.
// First go inGoldField.prototype.DeleteMover and replace the ugly delete index by
this.movers.splice(id, 1);
// Then in the Mover.prototype.Destroy
// This part is a a little blurred for me.
// the current HTMLtag looks good but when I console.log like this
console.log('before', this.HTMLtag);
this.HTMLtag = document.querySelector("#mover" + this.myID);
console.log('after', this.HTMLtag);
// They are not equal look like the first is outdated
You should convert all your delete and add to splice and push methods.
This is just a quick debug I don't know why the selector is outdated.
So I check the code again and I make it work without refreshing the selector. The problem is caused by the creation of dom element with innerHTML.
First reset
this.HTMLtag.src=this.destroyURL
Then instead of
//Mover.prototype.Destroy
this.tag.innerHTML+="<img id='mover"+this.moverCount+"'>";
I create a img dom el.
var img = document.createElement("img");
img.setAttribute('id', 'mover' + this.moverCount);
this.tag.appendChild(img);
All Monsters are now deleted with the image.
I don't check for the hero but first you should update your innerHTML and reply if there is still a problem. I don't think there is any problem with some prototype.

JavaScript callback function with relative variables

I am not entirely sure how to phrase this question, but basically, I have a class, button that on its click should call the function passed to it.
button = function(...,callBack) {
//...
this._cb = callBack;
}
button.prototype.callBack = function(e) {
//...
this._cb();
}
and then somewhere else
//on canvas click
e.target.callBack(e);
(I hope this is about the right amount of background, I can give more if needed)
So the issue I am running into is when I dynamically instantiate the buttons such that their callbacks use data from an array. i.e.
for (var i = 0; i < levels.length; i++) {
buttons[buttons.length] = new button(..., function() {drawLevel(levels[i])});
}
Then when they are clicked, they run that callback code and try to find some random value for i (probably a for-loop that didn't use var) and runs that level.
My question is, how can I (without using eval) circumvent this problem.
Thanks!
I'm not 100% clear on what you're asking, but it looks like you're going to be getting the wrong value for i in the anonymous function you're creating in the loop (it will always be levels.length)
Way around this is to have a different scope for every function created, with the i in each scope being a copy of the i in the loop
buttons[buttons.length] = new button(..., (function(i){
return function() {drawLevel(levels[i])};
})(i));

Why is 'this' not updating to refer to a new object?

I'm writing an online game which allows a user to progress from one puzzle to the next, and if the user makes mistakes, each puzzle has a start again button to allow the user to start just that puzzle from scratch. A simplified version of the code's structure is below:
function puzzle(generator) {
this.init = function() {
this.generator = generator;
...
this.addListeners();
}
//fires when the puzzle is solved
this.completed = function() {
window.theSequence.next();
}
this.empty = function() {
//get rid of all dom elements, all event listeners, and set all object properties to null;
}
this.addListeners = function() {
$('#startOver').click(function() {
window.thePuzzle.empty();
window.thePuzzle.init();
});
}
this.init();
}
function puzzleSequence(sequenceGenerator) {
this.init = function() {
//load the first puzzle
window.thePuzzle = new puzzle({generating json});
}
this.next = function() {
//destroy the last puzzle and create a new one
window.thePuzzle.empty();
window.thePuzzle = new puzzle({2nd generating json});
}
}
window.theSequence = new puzzleSequence({a sequence generator JSON});
The problem I have is that if the user has progressed to the second puzzle, if they click start over it loads the first puzzle rather than the second. After a bit of debugging I've worked out that 'this', when used in methods by the second puzzle, for some reason still holds a reference to the first puzzle, but 'window.thePuzzle' - which should be the same as this - correctly refers to the second puzzle.
Why is 'this' persisting in referrring to the first one?
Let me know if you need more code samples
$('#startOver').click(this.empty);
You've taken the empty method and detached it from this to pass as a plain unbound function to jQuery. When it gets called back, it will have no reference to the original value of this. In fact, when a function is called unbound, this will refer to window, so you'll be scribbling what you think are properties onto the globals.
JavaScript doesn't bind methods in the same way as other languages. See eg. this answer for an explanation of what it actually does. This confuses many people; personally I consider it one of JavaScript's worst flaws.
There is a very good (and clear) description of exactly how the this reference is treated in different contexts over at Quirksmode.

Categories

Resources