I am using this function to store animation functions and call it one by one for sequential animation.
I am not sure what I am missing in the below code. I wanted it to be a callback function.
currently this method runs only once.
function treasure(){
var blinky = function ()
{
if (funqueue.length > 0)
{
((funqueue.shift())(), blinky);
}
else { return }
}
blinky();
}
Thanks..
If it's intended as a callback, it should be passed within the calling parenthesis rather than after. (Also, the extra, wrapping parenthesis aren't really necessary.)
funqueue.shift()(blinky);
As is, blinky is just the 2nd value for the comma operator and nothing happens with it.
And, if it's not a callback, but rather just needs to be called after each function in funqueue, then just:
funqueue.shift();
blinky();
Related
still floundering around in my attempts to understand functions. How would I go about constructing a function with values I want to pass to it?
var box = $('#box1');
function pushCard(arg1) {
if (this.style.opacity == 0.5) {
this.style.opacity = 1;
}
else {
this.style.opacity = 0.5;
window.alert(arg1);
}
}
box.click(pushCard('String'));
tl;dr: be aware of the difference between functions / function results and when functions are passed as values / when they're called (and their result is passed)
The culprit is with this line:
box.click(pushCard('String'));
You're calling box.click() with "something". JavaScript needs to evaluate expressions before passing them as function arguments.
In this case, you instruct JavaScrip to run box.click(pushCard('String')) = call box.click with value of pushCard('String') as first parameter.
In order to do this, JavaScript first needs to evaluate pushCard('String') by running pushCard with value 'String' as first parameter (this doesn't need more evaluation since it's already a value).
The result of pushCard('String') is undefined (you're returning nothing from that function). So in effect, it's the equivalent of box.click(undefined).
This is what you want:
box.click(function() {pushCard('String')});
(or with ES6 arrow functions: box.click(() => pushCard('String'));)
In this case, you're assigning box.click() a function. This is what jQuery .click()` expects, a click handler which will run (be evaluated) then the click happens, not when the click handler is assigned.
In JavaScript, you can pass functions as values, and they are evaluated when explicitly called:
function test() {
alert('I have been called');
}
function delay_it(handler) {
setTimeout(function() {
handler(); // ⇽ call given function
}, 1000);
// shorter: setTimeout(handler, 1000);
}
// Wrong (we're calling test() by ourselves and passing the result (undefined) to delay_it
delay_it(test());
// Correct (we're giving test as function to delay_it, delay_it will call the function)
delay_it(test);
I want to pass arguments to an event listener in Javascript.
I have found solutions however I cannot understand why or how they work and why other solutions do not work.
I have a C/C++ background, however in Javascript functions perform a lot different.
Could you please help me understand how and why the following examples work or not work?
Without parameters the code works fine:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction);
function eventFunction()
{
console.log("works");
}
If I include brackets on the eventListener the function executes once without the event firing and then does nothing:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction());
function eventFunction()
{
console.log("works");
}
I suppose this is because the function is invoked at the eventListener and this doesn't allow the eventListener function to call the eventFunction later.
When I want to pass an argument with the eventFunction.
The following does not work:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction("works"));
function eventFunction(myStr)
{
console.log(myStr);
}
It invokes the eventFunction without the event firing and then when the events fires does nothing which is the same behaviour as previously.
So far I think I can understand.
To pass the argument correctly in the event function I found the following code.
The following solution works fine:
var clicker = document.getElementById("mainNavToggle");
clicker.addEventListener("click", eventFunction("works"));
function eventFunction(myStr)
{
function func(event, myStr)
{
console.log(myStr);
}
return function(event) {
func(event,myStr)
};
}
The first thing I notice is that this eventFunction is not invoked immediately.
Why is that?
However, I am at a loss of how arguments are passed within each function.
I understand that the "works" literal is passed to myStr on the eventFunction.
However in the function func there is an additional event parameter not passed by eventFunction.
Also why does the eventFunction needs to return another function with one parameter that internally calls the func function?
Also please notice that I want to pass parameters to the eventFunction that do not have a global scope, otherwise there would not be a need to pass parameters.
Let's dissect this step-by-step.
General information
The .addEventListener function/method takes in 2 (or 3) arguments:
a String, with a value of the event type/name (for example "click")
a pointer to a Function, which should be executed when the event occurs
a Boolean, specifying if event bubbling or event propagation should be used. This argument is optional and can be omitted.
Example 1
In you first example, you pass a String ("click") and a pointer to a Function (eventFunction) into .addEventListener. Everything works as expected, the event occurs and the function is executed.
Example 2
In this example, you pass a String and undefined to .addEventListener, which is not what .addEventListener expects. You might ask yourself "When did I pass in undefined?". Well, it happened as soon as you executed your eventFunction by adding parens () after it. Here, the syntax in JS is equal to the syntax in most other languages, adding parens to the right side of a function executes the function.
Because your eventFunction doesn't return anything explicitly, it automatically returns undefined. Therefor, this is what .addEventListener got:
clicker.addEventListener("click", undefined)
However, because you executed eventFunction, it logs "It worked" to the console once.
Example 3
This example fails for the same reasons example 2 failed.
Example 4
The code in the last example works, because it uses a concept called closure. There are literally thousands of explanations of the concept, so my explanation here will be really short. If you have trouble understanding it, just google for
"javascript closures".
In contrast to a lot of other languages, JavaScript has function scoping instead of block scoping. So if you define a variable inside a function F and return another function G from within F, G will have access to variables and arguments from inside F. For demonstration purposes:
function F () {
var fVariable = 'Defined in F';
return function G () {
return fVariable; // <-- this is the closure
}
}
var funcG = F(); // <-- execute F
funcG(); // -> 'Defined in F';
Compare this short example with your fourth code: They are pretty much the same. The only difference is, that your code creates an extra function func.
Now, if you call clicker.addEventListener("click", eventFunction("it works")),
you execute eventFunction, which returns another (anonymous/lambda) function which encapsulates the "it works" string via a closure. If we write it out by hand, this is what .addEventListener "sees":
clicker.addEventListener("click", function (event) {
func("it works", event);
});
EDIT: Solving the problem
Here's a working example, note that I changed the function names to reflect their purpose:
function eventHandlerFactory (myStr) { // <-- eventFunction in your code
return function executeOnEvent (event) { // <-- executed if an event occurs
console.log(myStr);
console.log(event);
}
}
clicker.addEventListener("click", eventHandlerFactory("it worked"));
The eventHandlerFactory function returns a function executeOnEvent, which is passed into .addEventListener. Writing it out by hand again, this is what the interpreter understands:
clicker.addEventListener("click", function executeOnEvent (event) {
console.log("it works");
console.log(event);
});
The first thing i notice is that this eventFunction is not invoked
immediately
It is invoked immediately. However, it returns a new function. This is the function that is registered for the click handler. This function, when called, calls the function func in a closure. The arguments passed to this function are also preserved in a closure.
To understand this concept, you need to understand how closures work. This is a topic that has been written about extensively, so I'll just point you to How do JavaScript closures work?.
Simply put...
why does
setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);
work perfectly, calling the function after the the specified delay, but
setTimeout(playNote(currentaudio.id,noteTime), delay);
calls the function playNote all at the same time?
(these setTimeout()s are in a for loop)
or, if my explanation is too hard to read, what is the difference between the two functions?
The first form that you list works, since it will evaluate a string at the end of delay. Using eval() is generally not a good idea, so you should avoid this.
The second method doesn't work, since you immediately execute a function object with the function call operator (). What ends up happening is that playNote is executed immediately if you use the form playNote(...), so nothing will happen at the end of the delay.
Instead, you have to pass an anonymous function to setTimeout, so the correct form is:
setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
Note that you are passing setTimeout an entire function expression, so it will hold on to the anonymous function and only execute it at the end of the delay.
You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:
setTimeout(playNote, delay);
Note:
For repeated events you can use setInterval() and you can set setInterval() to a variable and use the variable to stop the interval with clearInterval().
You say you use setTimeout() in a for loop. In many situations, it is better to use setTimeout() in a recursive function. This is because in a for loop, the variables used in the setTimeout() will not be the variables as they were when setTimeout() began, but the variables as they are after the delay when the function is fired.
Just use a recursive function to sidestep this entire problem.
Using recursion to deal with variable delay times:
// Set original delay
var delay = 500;
// Call the function for the first time, to begin the recursion.
playNote(xxx, yyy);
// The recursive function
function playNote(theId, theTime)
{
// Do whatever has to be done
// ...
// Have the function call itself again after a delay, if necessary
// you can modify the arguments that you use here. As an
// example I add 20 to theTime each time. You can also modify
// the delay. I add 1/2 a second to the delay each time as an example.
// You can use a condition to continue or stop the recursion
delay += 500;
if (condition)
{ setTimeout(function() { playNote(theID, theTime + 20) }, delay); }
}
Try this.
setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
Don't use string-timeouts. It's effective an eval, which is a Bad Thing. It works because it's converting currentaudio.id and noteTime to the string representations of themselves and hiding it in the code. This only works as long as those values have toString()s that generate JavaScript literal syntax that will recreate the value, which is true for Number but not for much else.
setTimeout(playNote(currentaudio.id, noteTime), delay);
that's a function call. playNote is called immediately and the returned result of the function (probably undefined) is passed to setTimeout(), not what you want.
As other answers mention, you can use an inline function expression with a closure to reference currentaudio and noteTime:
setTimeout(function() {
playNote(currentaudio.id, noteTime);
}, delay);
However, if you're in a loop and currentaudio or noteTime is different each time around the loop, you've got the Closure Loop Problem: the same variable will be referenced in every timeout, so when they're called you'll get the same value each time, the value that was left in the variable when the loop finished earlier.
You can work around this with another closure, taking a copy of the variable's value for each iteration of the loop:
setTimeout(function() {
return function(currentaudio, noteTime) {
playNote(currentaudio.id, noteTime);
};
}(currentaudio, noteTime), delay);
but this is getting a bit ugly now. Better is Function#bind, which will partially-apply a function for you:
setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);
(window is for setting the value of this inside the function, which is a feature of bind() you don't need here.)
However this is an ECMAScript Fifth Edition feature which not all browsers support yet. So if you want to use it you have to first hack in support, eg.:
// Make ECMA262-5 Function#bind work on older browsers
//
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
if (arguments.length<=1) {
return function() {
return that.apply(owner, arguments);
};
} else {
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
};
}
};
}
I literally created an account on this site to comment on Peter Ajtai's answer (currently highest voted), only to discover that you require 50 rep (whatever that is) to comment, so I'll do it as an answer since it's probably worth pointing out a couple things.
In his answer, he states the following:
You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:
setTimeout(playNote, delay);
This isn't true. After giving setTimeout a function reference and delay amount, any additional arguments are parsed as arguments for the referenced function. The below would be better than wrapping a function call in a function.
setTimeout(playNote, delay, currentaudio.id, noteTime)
Always consult the docs.
That said, as Peter points out, a recursive function would be a good idea if you want to vary the delay between each playNote(), or consider using setInterval() if you want there to be the same delay between each playNote().
Also worth noting that if you want to parse the i of your for loop into a setTimeout(), you need to wrap it in a function, as detailed here.
It may help to understand when javascript executes code, and when it waits to execute something:
let foo2 = function foo(bar=baz()){ console.log(bar); return bar()}
The first thing javascript executes is the function constructor, and creates a function object. You can use either the function keyword syntax or the => syntax, and you get similar (but not identical) results.
The function just created is then assigned to the variable foo2
At this point nothing else has been run: no other functions called (neither baz nor bar, no values looked up, etc. However, the syntax has been checked inside the function.
If you were to pass foo or foo2 to setTimeout then after the timeout, it would call the function, the same as if you did foo(). (notice that no args are passed to foo. This is because setTimeout doesn't by default pass arguments, although it can, but those arguments get evaluated before the timeout expires, not when it expires.)
After foo is called, default arguments are evaluated. Since we called foo without passing arguments, the default for bar is evaluated. (This would not have happened if we passed an argument)
While evaluating the default argument for bar, first javascript looks for a variable named baz. If it finds one, it then tries to call it as a function. If that works, it saves the return value to bar.
Now the main body of the function is evaluated:
Javascript looks up the variable bar and then calls console.log with the result. This does not call bar. However, if it was instead called as bar(), then bar would run first, and then the return value of bar() would be passed to console.log instead. Notice that javascript gets the values of the arguments to a function it is calling before it calls the function, and even before it looks up the function to see if it exists and is indeed a function.
Javascript again looks up bar, and then it tries to call it as a function. If that works, the value is returned as the result of foo()
So, function bodies and default arguments are not called immediately, but everything else is. Similarly, if you do a function call (i.e. ()), then that function is executed immediately as well. However, you aren't required to call a function. Leaving off the parentheses will allow you to pass that function around and call it later. The downside of that, though, is that you can't specify the arguments you want the function to be called with. Also, javascript does everything inside the function parentheses before it calls the function or looks up the variable the function is stored in.
Because the second one you're telling it to call the playNote function first and then pass the return value from it to setTimeout.
I used this code (followed by an xmlhttprequest that fills the "tcap" textarea):
st=setTimeout(checkme(),4000)
where checkme() is:
function checkme() {
if (typeof (st) != 'undefined') clearTimeout(st)
if (document.getElementById("tcap").innerHTML.length > 0) {
document.getElementById('waitmsg').style.display = 'none'
} else {
st = setTimeout(checkme(), 1000)
}
}
If I run it, it freezes Firefox 19 with no error message.
But if I replace the first argument (both in code and in the checkme() function) with:
st=setTimeout(function(){checkme()},4000)
it works correctly.
So my question is: what's the difference in calling the checkme() function with or without the anon function? Why in the first case it freezes Firefox?
Thanks
You need to remove the parens in
st=setTimeout(checkme(),4000)
so instead:
st=setTimeout(checkme,4000)
otherwise, the function is invoked right away.
Since you have the same error inside the checkme function, it probably kills your browser due to unbounded recursion.
setTimeout accepts a function as an argument, and the correct way to pass a function as an argument is either defining it as an anonymous function, or just providing the function name. If you use parenthesis(brackets), you aren't actually passing a function: You are executing the function and passing the result of the function to setTimeout.
Hence, when specifying a function in setTimeout, and anywhere else you need to pass a function as an argument, you should not use parenthesis.
You shouldn't be using the parenthesis within the setTimeout function. You should only be passing in a reference to the method. What you are doing is invoking the method and passing the return value in to the set timeout method.
If you are using setTimeout(checkme(),4000), you are passing the return value of checkme();
But if you want to pass it as a function you need to do in following ways
setTimeout(function(){checkme()},4000)
or
st=setTimeout(checkme,4000)
I got this piece of script (runned locally):
<script>
last = 0;
function uploadnew(){
var randomnumber=Math.floor(Math.random()*6);
if(randomnumber != last){
document.forms['f'+randomnumber].submit();
} else { uploadnew(); }
}
setInterval (uploadnew(), 1000*60*5);
</script>
But it seems that setInterval is not working / send form function doesn't work...
Any help will be appreciated!
Thanks!
You need to call setInterval() without parenthesis on your function, like this:
setInterval(uploadnew, 1000*60*5);
Using parenthesis you're calling it immediately and assigning the result (undefined) to be run on an interval, instead don't use parenthesis to pass the function itself, not the result of the function.
You need to remove the () after uploadnew within the setInterval call:
setInterval (uploadnew, 1000*60*5);
In JavaScript, functions are first-class objects which can be passed to other functions. In this example, you want to pass the function itself to setInterval, not call it first and then pass its return value.
Using setInterval ("uploadnew()", 1000*60*5); is not recommended because it is a "hidden" form of eval. Eval is evil and you shouldn't use it if you don't have to.
You need to pass a reference to the function instead of calling it.
This:
setInterval (uploadnew(), 1000*60*5);
should be:
setInterval (uploadnew, 1000*60*5);
If you were to call it as you were, you would need to have uploadnew() return a function to be passed to setInterval.
function uploadnew() {
return function(){
var randomnumber=Math.floor(Math.random()*6);
if(randomnumber != last) {
document.forms['f'+randomnumber].submit();
} else { uploadnew()(); }
}
}
Note the change to the recursive call.
Use
setTimeout ( expression, timeout );