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>
Related
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>
Is there any way to print out the value of the array players like in the example below? I've tried to find a solution for hours now...
function Room(name, id, owner) {
this.players = [];
this.movementz = function() {
console.log(this.players);
}
}
I'm calling the function using setInterval, like this:
setInterval(room.movementz, 1000);
The problem here is about the this object: creating your object and manually calling it's movementz method will work because the this element is the object itself, but using setInterval will cause the method to be called with this === window.
Here is an example:
var room = new Room();
room.movementz(); // []
setInterval(room.movementz, 1000); // undefined
This happens because when the movementz method gets called by setInterval, the this object is window, so, to fix this, you'll have to force the function to be called using room as this. You can easily accomplish this using the bind method, here's an example:
var room = new Room(),
players = "hello";
setInterval(room.movementz, 1000);
// this will output "hello" because this === window
setInterval(room.movementz.bind(room), 1000);
// this will output [], because now this === room
Here is my Test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
Here is the code
setTimer: function (boxNum, seconds) {
anotherObject.status.timer.boxNum = boxNum;
anotherObject.status.timer.seconds = seconds;
anotherObject.status.timer.visible = true;
},
Here is the error I am getting
TypeError: Cannot read property 'timer' of undefined
I tried setting the object using anotherObject = {} I tried setting anotherObject.status = {} and lastly tried setting anotherObject.status.timer = {}, however I still get the error. Any ideas, how can I mock the object?
Without knowing how/where 'anotherObject' is constructed I would think that you would need to initialize the 'anotherObject' before you execute the setTimer function in your test.
Do you have an init() or setup() function that exists on 'anotherObject' that would initialize the 'timer' object for you?
Although the method looks like it is just trying to make sure that the method is setting all the corresponding properties.
You could do the following before calling setTimer in your test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
//Initialize the anotherObject
anotherObject.status = { timer : {} }
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
This of course comes with the caveat that you have now defined an 'anotherObject' inside your test using the global scope (since excluding the var on any variable definition in javascript makes it global scope). This could effect other test cases that expect the timer object to be setup a certain way but your test case has now set the timer values to 1 and 15 respectively (could be alot of other values all depending on what the test case is doing).
So to help with this, resetting the 'anotherObject' at the beginning or end of your tests would help with pollution
afterEach(function(){
anotherObject.status = { timer : {} }
})
or
beforeEach(function(){
anotherObject.status = { timer : {} }
})
Of course if you have an init(), create() or setup() function on the 'anotherObject' that could be used it would of course give you more realistic results since the object would be much closer to what it would look like in production.
You are not working on the same "anotherObject" object in both source and test codes.
Each code has it's own object and setting values to one will not set in the other.
Im wondering how to properly "Clear" a object instance.
With the code below, the internal setInterval() will continue to run even after the instance is "cleared" by the parent.
// simple Class
var aClass = function(){
return {
init: function(){
console.log("aClass init()")
setInterval( this.tick, 1000 );
// note: we're not storing the ref
},
tick: function(){
console.log("aClass tick");
}
}
}
// instantiate the class
var inst = new aClass();
inst.init();
// try to forget the instance
function test(){
console.log("test() 1 inst:", inst);
inst = null;
console.log("test() 2 inst:", inst);
}
// run for a while, then call test()
setTimeout( test, 4000 );
Output:
aClass init()
aClass tick
aClass tick
aClass tick
test() 1 inst: {.....}
test() 2 inst: null
aClass tick
aClass tick ...
Problem is that the "aClass tick" message continues to print after the test().
Ideas?
Your instance is being kept in memory because the function you passed to setInterval is being kept in memory and has a reference to it. That function is referenced by the browser's list of active interval timers.
You'll need to remember the interval handle:
this.intervalHandle = setInterval( this.tick, 1000 );
...then later when you're dropping your reference to inst, you'll want to tell it that:
inst.cleanup();
inst = null;
...and in inst.cleanup:
clearInterval(this.intervalHandle);
That way, the browser will remove its reference to the function you passed to setInterval, which means that function will become eligible for garbage collection (based on your code there are no other references to it). And that means the reference it has to your instance is released, and so if no other outstanding references to that instance exist, it's eligible for garbage collection.
You should use clearInterval() rather than try to delete the reference.
This has been answered before - see
Stop setInterval call in JavaScript
That's because the browser itself keeps track of all the functions scheduled with setInterval and setTimeout. (Otherwise you'd have to store the function yourself somewhere, which in most cases would be pretty annoying.) And your function has a reference to a method, so that method stays alive. But I suspect the rest of the object would have been thrown away, though that's a bit difficult to prove.
If you ever plan to unschedule such a function, you need to do so explicitly:
this.interval = setInterval(function, 4000);
// and then later, in some `destroy()` method
clearInterval(this.interval);
And, by the way, you should very rarely need to return a big hash like that from a constructor! Work with the prototype instead:
var SomeClass = function() {
// ...
};
SomeClass.prototype.method = function() {
console.log('hello from', this);
};
My guess is that your instance of aClass is gone since there is no way to access any of it's properties but in init the tick function is copied when used as a parameter to setinterval.
var tickfunction=null;
var aClass = function(){
return {
init: function(){
console.log("aClass init()")
tickfunction = this.tick;
},
tick: function(){
console.log("aClass tick");
}
}
}
// instantiate the class
var inst = new aClass();
inst.init();
// try to forget the instance
console.log("test() 1 inst:", inst);
inst = null;
console.log("test() 2 inst:", inst);// its null isn't it?
console.log("the tickfunction:",tickfunction);
To illustrate that the function is copied:
function toInterval(){
console.log("orig");
}
setInterval(toInterval,1000);//prints orig every second
toInterval=function(){
console.log("changed it");
}
toInterval();//prints changed it
I wanted to call a function when all required images are loaded. The number of images is known in advance, so I tried attaching a function call to the onload event of each image and count the number of times it was called.
<html>
<head>
<script>
var tractor;
function Tractor()
{
this.init_graphics();
}
Tractor.prototype.init_graphics = function()
{
this.gr_max = 3;
this.load_count = 0;
this.loading_complete(); // #1 test call, works OK
this.img1 = new Image();
this.img1.onload = this.loading_complete; // #2 gets called, but gr_max = undefined, load_count = NaN
this.img1.src = "http://dl.dropbox.com/u/217824/tmp/rearwheel.gif"; //just a test image
}
Tractor.prototype.loading_complete = function()
{
this.load_count += 1;
alert("this.loading_complete, load_count = " + this.load_count + ", gr_max = " + this.gr_max);
if(this.load_count >= this.gr_max) {this.proceed();}
};
function start()
{
tractor = new Tractor();
}
</script>
</head>
<body onload="start();">
</body>
</html>
When it's just called from another function of the object (see #1), it works just as I expected. When, however, it's called from onload event (see #2), the variables become "undefined" or "NaN" or something. What's happening? What am I doing wrong? How do I make it work?
I don't remember ever creating my own objects in Javascript before, so I certainly deeply apologize for this "what's wrong with my code" kind of question. I used this article as a reference, section 1.2, mainly.
Just in case, I put the same code on http://jsfiddle.net/ffJLn/
bind the context to the callback:
this.img1.onload = this.loading_complete.bind(this);
See: http://jsfiddle.net/ffJLn/1/ (same as yours but with this addition)
Here's an explanation of how bind works in detail: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
The basic idea is that it makes this in the bound function equal to whatever you pass as the parameter to bind.
Another option is to create a closure:
var self = this;
this.img1.onload = function() { self.loading_complete() };
Closures are functions that keep references to their context (in fact, all functions in javascript work this way). So here you are creating an anonymous function that keeps a reference to self. So this is another way to maintain context and for loading_complete to have the right this.
See: http://jsfiddle.net/ffJLn/2/ (same as yours but with the second possibility)
When #2 gets called, your this has changed. this now refers to the new Image() rather than the Tractor object.
Try changing...
this.img1.onload = this.loading_complete;
to
var that = this;
this.img1.onload = function() { that.loading_complete(); };
You can now use es6 arrow functions which provide lexical binding:
this.img1.onload = () => { this.loading_complete(); };