So I checked a thread on here about global and local variables but didn't really find a solution to my problem. I just want a private or local variable to increment so that a function only fires once. I'll paste what I'm trying to achieve here any help would be much appreciate also please go easy on me I'm brand new to JavaScript. This code works but the variable I seems to be shared between functions.
function phonefun(){
i++;
console.log(i);
wage = wage - phone;
console.log(wage);
display();
document.getElementById('phone').style.backgroundColor = "darkgrey";
}
function waterfun(){
i++;
console.log(i);
wage = wage - water;
console.log(wage);
display();
document.getElementById('water-aid').style.backgroundColor = "darkgrey";
}
...the function is called on the click of a button, I want it so you
can only press the button
I think what you want to do is have your event handler unbind from the button after if fires. Thas is much better solution than counting how many times it's been clicked. Check out this link for how to bind and unbind event handlers using "vanilla" JS: https://plainjs.com/javascript/events/binding-and-unbinding-of-event-handlers-12/
In reference to your earlier questions...
A variable created inside of a function is said to be "scoped" to that function, which means that nothing outside of that function can access the variable. However, by initializing your variable without using the var or let keyword (the latter is ES6 syntax), you created an implicit global. This means that you inadvertently made it a global variable when you wanted it to be function-scoped.
Declaring a variable does not automatically assign a value of zero. If you do not assign a value, the value will be undefined.
If you had declared / assigned the variable thusly,var i = 0; or let i = 0; you would have had a properly scoped variable with an initial value of 0. The problem is, each time that function executed, the value would be reset to zero. To get the value to "stick" you would have to create state. You could do that by creating an object with getter and setter methods or by using a closure. However, the unbind solution seems to be the best way to go for what you want to do here.
I hope this helps.
To do what you want, you need a variable with a higher scope than the function so that the value can persist between function calls. A local variable will be garbage collected as the function returns and so, your counter would be lost.
var counter = 0; // This variable exists in a higher scope than the function
function loanfun(){
counter++;
if (counter == 1) {
console.log("function has run " + counter + " times.");
}
}
loanfun(); // Will run
loanfun(); // Won't run
you can make a class
function myStuff(){
this.i = 0,
this.loanfun = function(){
this.i++;
if (this.i == 1) {
wage = wage - loan;
console.log(wage);
display();
document.getElementById('loan').style.backgroundColor = "darkgrey";
}
}
}
var s = new myStuff();
s.loanfun();
s.loanfun();
You could try namespacing within an object:
var PageModule = {
count: 0,
loadfun: function (wage, loan) {
PageModule.count += 1;
if (PageModule.count === 1) {
console.log('execute!');
wage = wage - loan;
console.log(wage);
display();
document.getElementById('loan').style.backgroundColor = "darkgrey";
}
}
};
PageModule.loadfun();
PageModule.loadfun();
PageModule.loadfun();
// if you want to attach the method to a button
document.getElementById('my-btn-id').addEventListener('click', PageModule.loadfun);
Alternatively, you could use the following approach:
function myclickhandler () {
// do whatever you want here ...
//remove handler from button, so that the next button clicks will not do anything
document.getElementById('my-btn-id').removeEventListener('click', myclickhandler);
}
// attach the method to a button
document.getElementById('my-btn-id').addEventListener('click', myclickhandler);
Hope that this is what you want to do.But if you want simply to call(invoke) you function once just call and it will be executed only one time.
wage = 10;
loan = 5;
i=0; //this is the global variable
function loanfun(){
let j = i +1; //j is local variable
if (j === 1) {
wage = wage - loan;
console.log(wage);
//display();
document.getElementById('loan').style.backgroundColor = "darkgrey";
}
}
loanfun(); //invoke the function here
<div id="loan">
hi I am here working as expected
</div>
Related
I receive a challenge to create a function (arrow function) that count the times it gets called or invoked. BUT, this function can't take any parameter or interact with outside scope likes the normal way below.
Normal way:
var count = 0;
var countTimesCalled = (count) => {
count+=1;
return count;
}
console.log(countTimesCalled(count))
Is it possible to have created a function that does not take any parameter and not interact with outside scope to count the times it gets called? Where to store the times (var count) on runtime and after runtime?
Please help!
Use an IIFE to hold the count variable in a local scope:
var countTimesCalled = (() => {
var count = 0;
return () => ++count;
})();
console.log(countTimesCalled());
console.log(countTimesCalled());
console.log(countTimesCalled());
not interact with outside scope
That is not possible, but you could do something that looks kind of like it does that:
var countTimesCalled = (count => _ => ++count)(0);
console.log(countTimesCalled());
console.log(countTimesCalled());
console.log(countTimesCalled());
In reality there are two arrow functions there, an outer one to hold the count variable that is called once, and an inner one that is returned and referenced by countTimesCalled. That inner one is interacting with a scope outside of itself, but only as far out as the outer arrow function.
You could use .bind to capture state, if you really wanted to avoid accessing another closure scope entirely, e.g.
var countTimesCalled = function() {
return this.counter++
}.bind({ counter: 1 });
Set an attribute to the function, i.e: countTimesCalled.i
var countTimesCalled = () => (countTimesCalled.i = (countTimesCalled.i || 0) + 1);
console.log(countTimesCalled())
console.log(countTimesCalled())
console.log(countTimesCalled())
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.
Basically, what I have is a setInterval inside a function. What I want to do is, control it's behavior from outside.
Here's what I have -
function wheee() {
var i = 1;
slideee = setInterval(function() {
sliderContent.style.marginLeft = margin(i);
if(i < imagesNoOneLess) {
i++;
} else {
i = 0;
}
}, 5000); }
Now, I want to clear the interval from outside the wheee() function. How can I do that?
I also want to run the interval again, from outside. How?
Global variables are not dangerous, but a pretty way of coding it if you only have one slider is to use an object literal to simulate a singleton object.
var Slider= {
slider: null,
Start: function(i) {
this.slider = setInterval(function() {
// Slider code
console.log('running: ' + i);
i++;
}, 1000);
},
Stop: function() {
window.clearTimeout(this.slider);
}
};
Slider.Start(1); // Start the slider
Slider.Stop(); // Stop the slider
Well the way you've got the code now, it'll probably just work, because you didn't declare "slideee" with var.
As long as you somehow export the return value from setInterval() you're OK. You can either make that variable explicitly global (better than having it be implicit), or else have your "wheee" function return the value to its caller.
Set the scope of slideee to be out of wheee.
Use objects in order to keep the global scope clean.
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>
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click', function() { install(id); } );
}
}
No idea why id can't reach 'install' in function(). I am trying to bind every button (id from 50 to 100) with a click event to trigger the install(id) function. But it seems the variable id cannot reach install function. While I hard code it:
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click', function() { install( 56 ); });
}
}
it works! Please tell me why.
What you made is one of the most common mistakes when using Javascript closures.
By the way the very fact that this mistake is so common is IMO a proof that it's indeed a "bug" in the language itself.
Javascript supports read-write closures so when you capture a variable in a closure it's not the current value of the variable that is captured, but the variable itself.
This means that for example in
var arr = [];
for (var i=0; i<10; i++)
arr.push(function(){alert(i);});
each of the 10 functions in the array will contain a closure, but all of them will be referencing the same i variable used in the loop, not the value that this variable was having at the time the closure was created. So if you call any of them the output will be the same (for example 10 if you call them right after the loop).
Luckily enough the workaround is simple:
var arr = [];
for (var i=0; i<10; i++)
arr.push((function(i) {
return (function(){alert(i);});
})(i));
using this "wrapping" you are calling an anonymous function and inside that function the variable i is a different one from the loop and is actually a different variable for each invocation. Inside that function i is just a parameter and the closure returned is bound to that parameter.
In your case the solution is therefore:
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click',
(function(id){
return (function() { install(id); });
})(id));
}
}
By not reaching the install(), I guess you mean you get all your install(id) behaves like install(100).
Reason why it doesn't work
This is caused by the javaSctipt closure. This line function() { install(id) } assign the id to the install() callback function. The id's value won't be resolved until install() is call when is far later after the loop is finished - the time when id has already reached 100.
The solution is create another closure the hold the current id value.
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
(function (id) {
$('#'+id).bind('click', function() { install(id); });
}) (id);
}
}
Here is a demonstration code:
var funcCollections = [];
for (id = 50; id < 100; id++)
{
if(true)
{
(function () {
var thatId = id;
funcCollections.push(function () {console.log(thatId,id)});
}) ();
}
}
// funcCollections[1]();
// 51 100
// undefined
// funcCollections[2]();
// 52 100
You can't pass a variable to the function you've bind. It loses the val. When you pass '56' it will be always 56, but when you pass a var, the JavaScript will not bind the value of the var in the loop.
When you loop over variables and you create anonymous functions(closure) that reference the loop variable they will reference the last value
also note that you don't limit scope the loop variable to the for loop(it's not declared with var) so that means that later modifications to that variable will be propagated to all closures.
take a look at this
It's down to variable scope.
The anonymous function you're binding to the click event of the $('#' + id) elements has no awareness of the id variable in the your sample code (assuming that your sample code is an excerpt from a function). Even if it did (e.g. you declared id outside of any function, giving it global scope), id would hold the value 100 when the click event was called, which isn't what you intend.
However, you could use $(this).attr('id') to get hold of the element's id value instead:
for (id = 50; id < 100; id++)
{
if($('#' + id).attr('class') == 'myField')
{
$('#' + id).bind('click', function()
{
install(parseInt($(this).attr('id')));
});
}
}
Check out the jQuery .bind() documentation, it shows how this can be used from within an event handler.