0

At the moment I have simple JavaScript class like this:

function MyClass() {

// ... some code ...

this.Create = function() {
  funcName = 'myTestFunc()';
  cTimer = setTimeout(funcName, 1000);
  }

// ... more code ...

var myTestFunc = function() {
  alert ('everything\'s OK!');
  }

// ... more code ...
}

and to test it I'm using this code:

x = new MyClass();
x.Create();

I have some troubles to execute this function by it's name. If I put just eval(funcName); instead of setTimeout call it works fine but can't figure out why it doesn't work this way.

Course, this is part of more complex code but rest of code is irrelevant to this problem.

My question is obvious - How to execute function by its name set as setTimeout function's argument? Is it possible?

Note: Making this function public (this.myTestFunc = ...) isn't an option!

Update:

funcName = "myTestFunc()"; is just an example. In real code it looks like funcName = getRandomEffectFunctionName();! It's just a random value.

Wh1T3h4Ck5
  • 8,399
  • 9
  • 59
  • 79
  • Because `setTimeout` evals the code string in the global context - where your private function is not accessible. Instead, pass the function itself: `setTimeout(myTestFunc, 1000)` (and use a mapping object if you need to access it by name) – Bergi May 09 '13 at 00:00

2 Answers2

5

Referring to the update:

Instead of setting:

 var funcName = "getRandomEffectFunctionNeme()";

Thus, setting a reference to the function's name you should do

 var funcRef = getRandomEffectFunctionNeme;

And set a reference to the function itself . Not only this avoids the issues setTimeout with strings has*. It also solves your closure issue since your code is structured in such a way the timeout has access to the function itself.

In your case, let's assume you have some functions that are filters, for example lowPass highPass and blur. In that case, instead of choosing a function name we would be choosing a function.

First, we store these functions in an array:

var filters = [lowPass,highPass,blur];

In JavaScript, functions are first-class objects, you can pass them around just like other objects.

Next, we'll get a random number

var chosen = Math.floor(Math.random()*3);//get a random number between 0 and 2

Finally, we'll choose the filter and invoke it

var filter = filters[chosen];
setTimeout(filter,1000);

( * just try debugging it, it basically invokes the compiler whenever ran and is painfully slow)


You just pass a function to setTimeout as a parameter, rather then a string, setTimeout(myTestFunc,1000) .

When calling Create it would have access to it anyway because they are in the same closure.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • I can't because funcName gets function's name from the list of functions. It's a string and it's different on every execution. – Wh1T3h4Ck5 May 08 '13 at 23:58
  • 2
    Pass a reference to the function not a string, that's the point. – elclanrs May 08 '13 at 23:58
  • @Wh1T3h4Ck5: If the name is passed/known only at runtime, how are you defining functions with different names? – Felix Kling May 08 '13 at 23:59
  • @Wh1T3h4Ck5 Keeping it simple. There is _no_ way to access a function's closure from outside that function without exposing it directly. In your case, your `setTimeout` already has access to all those functions, and `setTimeout` accepts a function as a parameter (rather than a string) which is much better practice anyway. – Benjamin Gruenbaum May 09 '13 at 00:01
  • @FelixKling that is ImageTransitions class. There's a list of image-effects and each of them have its own function. This part of code actually uses random value (random name from the list of effects/functions) and that's the reason why I can't know which one is Math.rand() going to choose. – Wh1T3h4Ck5 May 09 '13 at 00:04
  • @Wh1T3h4Ck5 I added code that might explain how this would work in _your_ particular use case. Note that the important thing you take from this is _the general idea_ of functions working as first class objects in JavaScript which you can use in really cool and powerful ways :) – Benjamin Gruenbaum May 09 '13 at 00:08
  • 1
    Thanks Benjamin... tested and that works (with some workarounds). – Wh1T3h4Ck5 May 09 '13 at 00:08
  • I don't know what reason the OP has to pass function name as string (maybe server side generated code that sets data-action property on elements) But if it has to be a string than it has to be a string :-) – HMR May 09 '13 at 00:22
  • @HMR There's a lot of functions this class uses to create some image-effects (transitions, 30+). I'm using this class with some kind of SlideShow. There's also a list of all effects and their functions and every time slideshow has to show another image it generates one random value from the list of effects and then runs its function. That's why I don't know what function name is going to be randomly selected. Now, because generator returns STRING, I've posted just sample-STRING assigned to the variable instead of tons of irrelevant code which also returns STRING (to make things simpler) – Wh1T3h4Ck5 May 09 '13 at 00:58
  • @Wh1T3h4Ck5 While we're doing cool stuff with closures and functions in JavaScript, you can even create a generator! For example in http://jsfiddle.net/yQuxz/ you can only pass the generator around instead of the list of filters or list of functions. You'd just call it and it'd give you one random filter function each time. Generators let you perform really powerful stuff in JavaScript and are also planned to get a boost in the next version of the language :) – Benjamin Gruenbaum May 09 '13 at 01:04
  • @BenjaminGruenbaum To be honest with you, I'm not much experienced with JavaScript. It's fun playing with code but I'm still a noob in client-side scripting. Some SlideShows with fade-effects in procedural mode are my top limit so far. Well, we all learn something every day ;) – Wh1T3h4Ck5 May 09 '13 at 02:14
2

NOTE: This solution is only applicable if you can not pass the function name as a function reference, for example if you're integrating with code that is outside your control. Generally, when possible, you should pass a function reference since in JavaScript, all functions are objects.

Assuming that the timeout and the function are in the same closure your code is pretty close. The problem is that your eval call executes in the global context because it is in a timer. This means they are no longer in the same lexical scope.

You can however, grab a reference to the function by clever use of eval which you can later call in the setTimeout invocation.

var F=eval(funcName);// gain a reference to the function given the function's name
cTimer = setTimeout(F, 1000);

If you're using AIR or don't trust the functionName string you can do the following:

function Test(){
 var functionContainer={
  t:function(){
   console.log("it's t");
  }
 };
 this.callT=function(functionName){
  var F=functionContainer[functionName];
  console.log("F is:",F);
  setTimeout(F,500);
 }
}
(new Test()).call("t");

This is preferable since you are invoking setTimeout with a function's name and not a string. In general, using setTimeout with a string can have issues, it's hard to debug or maintain.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
HMR
  • 37,593
  • 24
  • 91
  • 160
  • 3
    Since `myTestFunc` is not a property of `this`, that wouldn't work. – Felix Kling May 08 '13 at 23:58
  • I see, hmmm. It's a bit more complicated to acces the function if you only have the function's name as string. – HMR May 09 '13 at 00:04
  • Updated the answer using eval, not sure if there is a better way of doing this. – HMR May 09 '13 at 00:09
  • @HMR That would call eval, F would contain the result of the expression inside eval so unless that expression _returns_ a function there is no meaning to F(), you can try to `.bind` to eval, (or just pass `eval.bind` to the timeout, or use a `Function` constructor , but that's still problematic because you're using eval where you don't need to (most times in my experience eval can be replaced by reference or bracket notation). Still, if you'd like to see a possible solution to invoke without `this`, you might find this useful http://stackoverflow.com/questions/15409639/15409716#15409716 – Benjamin Gruenbaum May 09 '13 at 00:13
  • Changed it to not use eval although eval does return a reference to the function as far as I can see (as long as you have a valid funtion name) – HMR May 09 '13 at 00:16
  • @HMR, +1 thanks for your effort. I'll certainly consider this solution either, even problem has been solved. – Wh1T3h4Ck5 May 09 '13 at 00:17
  • Interesting, nice idea using `eval` on a function's name to get a reference to it. The function has to be in the same lexical closure (well, LexicalEnvironment if we're formal (http://es5.github.io/#x10.4.2)) as the caller. I'm making a small edit to the code, clever idea +1. – Benjamin Gruenbaum May 09 '13 at 14:51
  • Thank you Benjamin, I rarely use eval but the OP mentioned the function being available in eval so tried to see if it would return a reference to it and it did. Do think that if it has to be a string then the object wrapper is nicer. If it can be a number than a direct function reference can be maintained in an array. – HMR May 09 '13 at 15:21