"Class" continues working without being referenced globally - javascript

I made an image slider "class" and originally instantiated it as:
var foo = new Slider(document.getElementById("featuredSlider"), 900);
I tried removing var foo = and it continues to work which was surprising to me. What makes it continue working? Is it a bad idea to not reference it globally?
On a side note, "featuredSlider" is the id of a <div> tag. It contains some number of <a> tags and each contain <img> tags.
function Slider(inElement, inStep) {
if (!inElement) return;
this.element = inElement;
this.start = 0;
this.end = 0;
var self = this;
var limit = inElement.getElementsByTagName("a").length*inStep;
setInterval(function() {
self.start = self.end;
self.end = (self.end+inStep)%limit;
self.animate(this.start < this.end ? 1 : -1);
}, 3000);
}
Slider.prototype.animate = function(inZeno) {
this.start += ((this.end-this.start)>>2)+inZeno;
this.element.style.left = this.start+"px";
if (this.start !== this.end) {
var self = this;
setTimeout(function() {
self.animate(self.start < self.end ? 1 : -1);
}, 33);
}
}
//new Slider(document.getElementById("featuredSlider"), 900);
var foo = new Slider(document.getElementById("featuredSlider"), 900);

I tried removing var foo = and it continues to work which was surprising to me. What makes it continue working?
Because references to it are kept by the timer system, because it's set up callbacks to functions (closures) that reference it via setInterval and setTimeout. So references to the necessary parts of it are kept around as long as those timers are still held.
The function it's giving to setInterval here:
setInterval(function() {
self.start = self.end;
self.end = (self.end+inStep)%limit;
self.animate(this.start < this.end ? 1 : -1);
}, 3000);
is a closure over the call to Slider. So all of the things in scope within the closure are kept around as long as the closure (function) is around. Since that timer is never cancelled, the timer system keeps those references alive. (If "closure" is unfamiliar or only slightly familiar, don't worry: Closures are not complicated.)
Is it a bad idea to not reference it globally?
No, that's fine. If it keeps working and you don't need the reference, there's no need to store a reference to it. This was quite a common pattern back when a lot of people used script.aculo.us, which used new for about half of its effects.

Related

Understanding JavaScript setTimeout and setInterval

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.

Integer returning as NaN when added

Writing some code, and when creating an instance of a class, something strange happens with an integer variable I have:
function Mat(x, y, spawner) {
this.x = x;
this.y = y;
this.val = 1;
this._spawner = spawner;
this.newborn = true;
this.bornTime = 0;
this.spawnTimer = setInterval("this.bornTime++; console.log(this.bornTime);", 1000);
}
Pretty cut and clear code; every second after an instance of the variable is created, it should increment the bornTime variable by 1 and log it.
Mat.prototype.update = function() {
if (this.bornTime >= 5) {
this.bornTime = null;
clearInterval(this.spawnTimer);
this.newborn = false;
console.log("Grown!");
}
}
This additional code would cause this instance to be "grown" after 5 seconds, however when I check the console, it reads that bornTime is not a number(NaN).
Why is this, and is there a solution that I am not seeing?
this inside the setTimeout code is not the same as outside (more info on MDN), so your code is actually calculating undefined++, which is NaN.
You have to create another variable, and pass a function to setTimeout instead of letting it eval a string (by the way, passing a function is supposed to be faster, and looks better):
var that = this;
this.spawnTimer = setInterval(function(){
that.bornTime++;
console.log(that.bornTime);
}, 1000);
I know this is 5 years old question but its 2018 and heres an Es6 syntax solution to avoid extra step of binding key word this.
this.spawnTimer = setInterval(() => {
this.bornTime++;
console.log(this.bornTime);
}, 1000);

Self-created fadeIn() function not working correctly

I am trying to create the fadeIn() function using Javascript. I am having trouble, when I click the fadeIn button, it does not perform a fadeIn animation, instead I have to click it several times to fadeIn. Would anyone know how I can fix this issue?
jsFiddle
// Created a jQuery like reference
function $(selector) {
if (!(this instanceof $)) return new $(selector); // if new object is not defined, return new object
this.selector = selector; // setting selector attribute
this.node = document.querySelector(this.selector); // finds single element from the DOM
};
var fInFrom = 0, fOutFrom = 10;
$.prototype.fadeIn = function() {
var target = this.node,
newSetting = fInFrom / 10;
// Set Default styles for opacity
target.style.display = 'block';
target.style.opacity = newSetting;
// fadeInFrom will increment by 1
fInFrom++;
var loopTimer = setTimeout('this.fadeIn', 50);
if (fInFrom === 10) {
target.style.opacity = 1;
clearTimeout(loopTimer);
fInFrom = 0;
return false;
}
return this;
}
$('#fadeIn').node.addEventListener('click', function() {
$('#box').fadeIn();
});
This line is your problem:
setTimeout('this.fadeIn', 50)
That will set a timeout to evaluate the expression this.fadeIn in the global scope in approximately 50 milliseconds from the current time. There's two problems with that:
It's in the global scope; this is window, not an instance of $, so this.fadeIn is undefined.
Even if it were resolved correctly, you're only evaluating this.fadeIn; you're not calling it. You would need to use this.fadeIn() for it to do anything. (If you do that with the current code, this will reveal your first problem.)
To solve this, pass not a string but a function that does what you want it to do. You might naïvely do this:
setTimeout(function() {
this.fadeIn();
}, 50);
Unfortunately, while we now have lexical scoping for variables, this in JavaScript is dynamic; we have to work around that. Since we do have lexical scoping for variables, we can utilize that: [try it]
var me = this; // store the current value of this in a variable
var loopTimer = setTimeout(function() {
me.fadeIn();
}, 50);
After that's solved, you might want to look into:
Not using global variables to hold the fade state. Even after that fix, running two fade animations at once on different elements won't work as expected. (Try it.)
Only setting the timeout if you need to; right now, you always set it and then clear it if you don't need it. You might want to only set it if you need it in the first place.

Reaching an Object's Property

In my application I have an object with several properties that get set in various places in the application.
In one of my prototype functions I have a function that runs in intervals to update a timer, and in that function the property (this.)theTime should be set. The problem is that this doesn't happen, and I guess the reason is that this.theTime points to the function itself instead of the object.
Below is two versions of my code, and neither of them works. Any tips for me?
// 1.
function changeTime() {
this.theTime = setTime(time);
time.setSeconds(time.getSeconds()+1);
p1.html(this.theTime);
}
interval = setInterval(changeTime(), 1000 );
// 2.
function changeTime(theTime) {
theTime = setTime(time);
time.setSeconds(time.getSeconds()+1);
p1.html(theTime);
}
interval = setInterval( function() { changeTime(this.theTime); }, 1000 );
...
Too make it more clear, the function above updates a timer (eg. 00:00:01 -> 00:00:02) every second, and I want this.theTime to be updated with the time.
When the timer stops (which happens in another prototype function) I want to be able to see what time the timer stopped on, but as it is now this.theTime is the default value, which means that the function above doesn't update the objects property. Instead this.theTime in the function above must be a local variable.
NOTE: setTime() is another function that exists in the same prototype function as the function above.
Well when you use this in some function this is referencing to the object which actually the function is. Here:
function myF() {
this.var = 'hey';
}
You can reach var using this (myF as a constructor function):
var obj = new myF();
alert(obj.var);
Or here:
function myF2() {
if (typeof this.var === 'undefined') {
this.var = 0;
} else {
this.var += 1;
}
alert(this.var);
}
Here var again is a property of myF2 (which as I said is not just a function because in JavaScript functions are objects).
Each time you call myF2 this.var is going to be incremented and alerted (just in the first call it's going to be initialized).
In the second function (anonymous function using in the second setInterval) you're doing the same.
One solution is to make theTime global in both cases so you don't need to use:
this.theTime
So the result can be something like this:
var theTime = 0, interval;
function changeTime() {
theTime += 1;
document.body.innerHTML = theTime;
setInterval
}
interval = setInterval(changeTime, 1000 );
http://jsfiddle.net/u3EuC/
You can verify easily by writting a
debugger;
to set a breakpoint in your functions. Then it may be pretty easy to find your problem.
You are correct in your assumption that there's something wrong with your this keyword. this in JavaScript is a bit tricky, so using it in functions (especially with setTimeout or setInterval is risky.
What you want to do is save the value of this when you create the function.
Here's more information: http://justin.harmonize.fm/index.php/2009/09/an-introduction-to-javascripts-this/
Maybe these comments will direct you to the right way
var theTime; // global variable
function changeTime() {
theTime = setTime(time); // theTime is global variable declared above (accesible from anywhere)
// var myTime = setTime(time); // myTime is local variable
time.setSeconds(time.getSeconds()+1);
p1.html(theTime);
}
interval = setInterval(changeTime, 1000 ); // no braces
Jason, after your clarification, I believe it is better to provide you whole new answer trying to explain this statement in JS as good (and simple) as possible. I hope it helps.
<html>
<body>
<div id="output1"></div>
<div id="output2"></div>
<script>
// theTime is undefined in global scope
function obj(target) {
var theTime = 0;
var that = this; // var means "private"
this.changeTime = function() { // here "this" points to obj and means "public"
theTime++; // no var => outer scope = obj scope
// here "this" points to changeTime function, not to obj!
// "that" points to obj, you may use that.theTime
document.getElementById(target).innerHTML = theTime;
}
}
var o1 = new obj("output1");
var o2 = new obj("output2");
setInterval(o1.changeTime,1000); // update output1 content every second
setInterval(o2.changeTime,500); // update output2 content twice a second
</script>
</body>
</html>

Recursion function not defined error

Hi i have a problem with recursion.
i followed this example from wc3 http://www.w3schools.com/jsref/met_win_settimeout.asp
But mine seems to not work at all.
function rotateImages(start)
{
var a = new Array("image1.jpg","image2.jpg","image3.jpg", "image4.jpg");
var c = new Array("url1", "url2", "url3", "url4");
var b = document.getElementById('rotating1');
var d = document.getElementById('imageurl');
if(start>=a.length)
start=0;
b.src = a[start];
d.href = c[start];
window.setTimeout("rotateImages(" + (start+1) + ")",3000);
}
rotateImages(0);
Firebug throws the error :
rotateImages is not defined
[Break On This Error] window.setTimeout('rotateImages('+(start+1)+')',3000);
However if i change the timeOut to :
window.setTimeout(rotateImages(start+1),3000);
It recursives but somehow the delay doesn't work and gives me too much recursion(7000 in a sec)
There are many reasons why eval should be avoided, that it breaks scope is one of them. Passing a string to setTimeout causes it to be evaled when the timer runs out.
You should pass a function instead.
window.setTimeout(rotateImages(start+1),3000);
This calls rotateImages immediately, then passes its return value to setTimeout. This doesn't help since rotateImages doesn't return a function.
You probably want:
window.setTimeout(rotateImages,3000,[start+1]);
Or create an anonymous function that wraps a closure around start and pass that instead:
window.setTimeout(function () { rotateImages(start + 1); },3000);
The latter option has better support among browsers.
Be wary of code from W3Schools.
The other answers give a solution. I'll just add that you're recreating the Arrays and repeating the DOM selection every time the rotateImages function is called. This is unnecessary.
You can change your code like this:
(function() {
var a = ["image1.jpg","image2.jpg","image3.jpg", "image4.jpg"];
var c = ["url1", "url2", "url3", "url4"];
var b = document.getElementById('rotating1');
var d = document.getElementById('imageurl');
function rotateImages(start) {
b.src = a[start];
d.href = c[start];
window.setTimeout(function() {
rotateImages( ++start % a.length );
}, 3000);
}
rotateImages(0);
})();
Try this syntax:
window.setTimeout(function() {
rotateImages(start+1);
},3000);
setTimeout() expects a function reference as the 1st parameter. Simply putting a function call there would give the return value of te function as the parameter, this is why the delay did not work. However your first try with evaluating a string was a good approach, but it is not recommended.

Categories

Resources