Is it possible to call a function in AS3 using a string value as the function name e.g.
var functionName:String = "getDetails";
var instance1:MyObject = new MyObject();
instance1.functionName(); // I know this is so wrong, but it gets the point accross:)
UPDATE
The answer from #Taskinoor on accessing a function is correct:
instance1[functionName]();
And to access a property we would use:
instance1[propertyName]
instance1[functionName]();
Check this for some details.
You may use function.apply() or function.call() methods instead in the case when you dont know whether object has such method for instance.
var functionName:String = "getDetails";
var instance1:MyObject = new MyObject();
var function:Function = instance1[functionName]
if (function)
function.call(instance1, yourArguments)
I have created the following wrappers for calling a function. You can call it by its name or by the actual function. I tried to make these as error-prone as possible.
The following function converts a function name to the corresponding function given the scope.
public static function parseFunc(func:*, scope:Object):Function {
if (func is String && scope && scope.hasOwnProperty(funcName)) {
func = scope[func] as Function;
}
return func is Function ? func : null;
}
Call
Signature: call(func:*,scope:Object,...args):*
public static function call(func:*, scope:Object, ...args):* {
func = parseFunc(func, scope);
if (func) {
switch (args.length) {
case 0:
return func.call(scope);
case 1:
return func.call(scope, args[0]);
case 2:
return func.call(scope, args[0], args[1]);
case 3:
return func.call(scope, args[0], args[1], args[2]);
// Continue...
}
}
return null;
}
Apply
Signature: apply(func:*,scope:Object,argArray:*=null):*
public static function apply(func:*, scope:Object, argArray:*=null):* {
func = parseFunc(func, scope);
return func != null ? func.apply(scope, argArray) : null;
}
Notes
Call
The switch is needed, because both ...args and arguments.slice(2) are Arrays. You need to call Function.call() with variable arguments.
Apply
The built-in function (apply(thisArg:*, argArray:*):*) uses a non-typed argument for the argArray. I am just piggy-backing off of this.
Related
I'm not sure of the best approach for handling scoping of "this" in TypeScript.
Here's an example of a common pattern in the code I am converting over to TypeScript:
class DemonstrateScopingProblems {
private status = "blah";
public run() {
alert(this.status);
}
}
var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run();
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run);
Now, I could change the call to...
$(document).ready(thisTest.run.bind(thisTest));
...which does work. But it's kinda horrible. It means that code can all compile and work fine in some circumstances, but if we forget to bind the scope it will break.
I would like a way to do it within the class, so that when using the class we don't need to worry about what "this" is scoped to.
Any suggestions?
Update
Another approach that works is using the fat arrow:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Is that a valid approach?
You have a few options here, each with its own trade-offs. Unfortunately there is no obvious best solution and it will really depend on the application.
Automatic Class Binding
As shown in your question:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the this context instead of each call site creating a new closure upon invoke.
Good: Impossible for external callers to forget to handle this context
Good: Typesafe in TypeScript
Good: No extra work if the function has parameters
Bad: Derived classes can't call base class methods written this way using super.
Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
Function.bind
Also as shown:
$(document).ready(thisTest.run.bind(thisTest));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: No extra work if the function has parameters
Bad: In TypeScript, this currently has no type safety
Bad: Only available in ECMAScript 5, if that matters to you
Bad: You have to type the instance name twice
Fat arrow
In TypeScript (shown here with some dummy parameters for explanatory reasons):
$(document).ready((n, m) => thisTest.run(n, m));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: In TypeScript, this has 100% type safety
Good: Works in ECMAScript 3
Good: You only have to type the instance name once
Bad: You'll have to type the parameters twice
Bad: Doesn't work with variadic parameters
Another solution that requires some initial setup but pays off with its invincibly light, literally one-word syntax is using Method Decorators to JIT-bind methods through getters.
I've created a repo on GitHub to showcase an implementation of this idea (it's a bit lengthy to fit into an answer with its 40 lines of code, including comments), that you would use as simply as:
class DemonstrateScopingProblems {
private status = "blah";
#bound public run() {
alert(this.status);
}
}
I haven't seen this mentioned anywhere yet, but it works flawlessly. Also, there is no notable downside to this approach: the implementation of this decorator -- including some type-checking for runtime type-safety -- is trivial and straightforward, and comes with essentially zero overhead after the initial method call.
The essential part is defining the following getter on the class prototype, which is executed immediately before the first call:
get: function () {
// Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
// instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
var instance = this;
Object.defineProperty(instance, propKey.toString(), {
value: function () {
// This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
return originalMethod.apply(instance, arguments);
}
});
// The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
// JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
return instance[propKey];
}
Full source
The idea can be also taken one step further, by doing this in a class decorator instead, iterating over methods and defining the above property descriptor for each of them in one pass.
Necromancing.
There's an obvious simple solution that doesn't require arrow-functions (arrow-functions are 30% slower), or JIT-methods through getters.
That solution is to bind the this-context in the constructor.
class DemonstrateScopingProblems
{
constructor()
{
this.run = this.run.bind(this);
}
private status = "blah";
public run() {
alert(this.status);
}
}
You can write an autobind method to automatically bind all functions in the constructor of the class:
class DemonstrateScopingProblems
{
constructor()
{
this.autoBind(this);
}
[...]
}
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
const val = self[key];
if (key !== 'constructor' && typeof val === 'function')
{
// console.log(key);
self[key] = val.bind(self);
} // End if (key !== 'constructor' && typeof val === 'function')
} // Next key
return self;
} // End Function autoBind
Note that if you don't put the autobind-function into the same class as a member function, it's just autoBind(this); and not this.autoBind(this);
And also, the above autoBind function is dumbed down, to show the principle.
If you want this to work reliably, you need to test if the function is a getter/setter of a property as well, because otherwise - boom - if your class contains properties, that is.
Like this:
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
if (key !== 'constructor')
{
// console.log(key);
let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
if (desc != null)
{
if (!desc.configurable) {
console.log("AUTOBIND-WARNING: Property \"" + key + "\" not configurable ! (" + self.constructor.name + ")");
continue;
}
let g = desc.get != null;
let s = desc.set != null;
if (g || s)
{
var newGetter = null;
var newSetter = null;
if (g)
newGetter = desc.get.bind(self);
if (s)
newSetter = desc.set.bind(self);
if (newGetter != null && newSetter == null) {
Object.defineProperty(self, key, {
get: newGetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else if (newSetter != null && newGetter == null) {
Object.defineProperty(self, key, {
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else {
Object.defineProperty(self, key, {
get: newGetter,
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
continue; // if it's a property, it can't be a function
} // End if (g || s)
} // End if (desc != null)
if (typeof (self[key]) === 'function')
{
let val = self[key];
self[key] = val.bind(self);
} // End if (typeof (self[key]) === 'function')
} // End if (key !== 'constructor')
} // Next key
return self;
} // End Function autoBind
In your code, have you tried just changing the last line as follows?
$(document).ready(() => thisTest.run());
package {
import flash.display.Sprite;
public class test1 extends Sprite {
private var tmp:Object;
public function test1() {
createObj(tmp);
if(tmp == null) {
trace("nothing changed");
}
}
private function createObj(obj:Object):void {
obj = new Object();
}
}
}
In the above code the output on the console is :
nothing changed
Why?
If the argument to createObj was passed by reference(which is the
default behavior of actionscript), why did it not get modified?
You don't pass a reference. You are passing null which is assigned to the local variable obj for use within the function.
Passing arguments by value or by reference:
To be passed by reference means that
only a reference to the argument is
passed instead of the actual value. No
copy of the actual argument is made.
Instead, a reference to the variable
passed as an argument is created and
assigned to a local variable for use
within the function.
In createObj you are creating a new reference which you must return:
public function test1() {
tmp = createObj();
if(tmp != null) {
trace("Hello World!");
}
}
private function createObj():Object {
return new Object();
}
is it posible to have callable objects on ActionScript? For example:
class Foo extends EventDispatcher
{
Foo() { super(); }
call(world:String):String
{
return "Hello, " + world;
}
}
And later...
var foo:Foo = new Foo();
trace( foo("World!") ); // Will NOT work
Why would you need to do this? (I'm not criticising, just interested!) Functions in AS3 are themselves first-class citizens, and can be passed around as arguments.
e.g.
public function main(foo:Function):void
{
trace(foo("World!")); // Will work, assuming foo = function(str:String):String {...}
}
No, only functions/methods can be called in this way. If the only reason is you want to type fewer characters, then you should shorten the length of the instance names and method names.
One option is to use a closure:
public function Foo():Function {
var bar:String;
return function (world:String):String {
var msg:String;
if (bar) {
msg = bar + ' says "Hello, ' + world + '"';
} else {
msg = "Hello, " + world;
}
bar = world;
return msg;
}
}
...
var foo = Foo();
trace( foo("World!") );
This is a much simplified case of the larger pattern of implementing objects as functions. As such, it's more useful in languages that support FP but not OOP, but does technically give you a callable "object". The syntax may be a little off, but:
public function createFoo(barInit, ...):Function {
var slots = {
greeter: barInit, ...
};
var methods = {
'get': function(name) { return slots[name]; }
'set': function(name, value) { slots[name] = value; }
greet: function(whom) {
var msg = slots.greeter + ' says "Hello, ' + whom + '"'
slots.greeter = whom;
return msg;
},
...
};
return function (method:String):* {
args = Array.splice.call(arguments, 1);
return methods[method].apply(null, args);
}
}
var foo = createFoo('Kermit');
trace(foo('greet', "World"));
trace(foo('greet', "Sailor"));
You probably don't want to do it in AS.
As others had said, you can't have callable objects. However, if for some reason you want to have stateful functions, you can achieve it with help of static class variables and package level functions. For example:
// com/example/foo/Helper.as
package com.example.foo {
public class Helper {
private static var _instance:Foo;
public static var data:String;
public static function get instance():Helper
{
if(!_instance) { _instance = new Helper(); }
return _instance;
}
}
}
// com/example/foo/hello.as
package com.example.foo {
public function hello(world:String):void
{
if(Helper.instance.data)
{
trace("Bye, " + Helper.instance.data);
}
trace("Hello, " + world);
Helper.instance.data = world;
}
}
When used, it will print different things.
hello("World!"); // traces "Hello, World!"
hello("People"); // traces "Bye, World!" and "Hello, People"
note: both the constructor and the method declaration miss the keywords public function to even compile, but I suppose that's not the original code. :)
the answer is: you can't.
my question is: what do you want to accomplish?
Functions are the only callable values. And Functions are primitives in ActionScript, much as ints, or Booleans, so there is no meaningful way to extend them.
If you want it to be an object, do it the Java way, defining an ICallable interface, and actually call a method, or just really use a function. closures provide the most simple and flexible possibility to create stateful functions, if that is what you want.
edit: well, you can do this (as an example):
private var fooInst:Foo = new Foo();
protected var foo:Function = fooInst.call;
and then the following workst as you wish:
<mx:Label text="{foo('Whatever')}"/>
its maybe even a little more flexible, although you lose the benefits of strict typing.
greetz
back2dos
Groovy offers some really neat language features for dealing with and implementing Java interfaces, but I seem kind of stuck.
I want to dynamically implement an Interface on a Groovy class and intercept all method calls on that interface using GroovyInterceptable.invokeMethod. Here what I tried so far:
public interface TestInterface
{
public void doBla();
public String hello(String world);
}
import groovy.lang.GroovyInterceptable;
class GormInterfaceDispatcher implements GroovyInterceptable
{
def invokeMethod(String name, args) {
System.out.println ("Beginning $name with $args")
def metaMethod = metaClass.getMetaMethod(name, args)
def result = null
if(!metaMethod)
{
// Do something cool here with the method call
}
else
result = metaMethod.invoke(this, args)
System.out.println ("Completed $name")
return result
}
TestInterface getFromClosure()
{
// This works, but how do I get the method name from here?
// I find that even more elegant than using invokeMethod
return { Object[] args -> System.out.println "An unknown method called with $args" }.asType(TestInterface.class)
}
TestInterface getThisAsInterface()
{
// I'm using asType because I won't know the interfaces
// This returns null
return this.asType(TestInterface.class)
}
public static void main(String[] args)
{
def gid = new GormInterfaceDispatcher()
TestInterface ti = gid.getFromClosure()
assert ti != null
ti.doBla() // Works
TestInterface ti2 = gid.getThisAsInterface()
assert ti2 != null // Assertion failed
ti2.doBla()
}
}
Returning the Closure works fine, but I couldn't figure a way to find out the name of the method being called there.
Trying to make a Proxy to the this reference itself (so that method calls will call invokeMethod) returns null.
You could use the Map coercion feature of Groovy to dynamically generate a Map that represents the given interface:
TestInterface getMapAsInterface() {
def map = [:]
TestInterface.class.methods.each() { method ->
map."$method.name" = { Object[] args->
println "Called method ${method.name} with ${args}"
}
}
return map.asType(TestInterface.class)
}
To complete the response of Christoph, as stated by this page, you can implement an interface with a closure. For example:
def map = [doBla: { println 'Bla!'}, hello: {world -> "Hello $world".toString()}] as TestInterface
map.hello 'Groovy' // returns 'Hello Groovy'
I need to return the value from my Responder object. Right now, I have:
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
Basically, I need to know how to get the return value of ret_pr into id or anything that I return from that function. The responder just seems to eat it. I can't use a public variable somewhere else because this will be running multiple times at once, so I need local scope.
This is how I'd write a connection to the AMF server, call it and store the resulting value. Remember that the result won't be available instantly so you'll set up the responder to "respond" to the data once it returns from the server.
public function init():void
{
connection = new NetConnection();
connection.connect('http://10.0.2.2:5000/gateway');
setSessionID( 1 );
}
public function setSessionID(user_id:String):void
{
var amfResponder:Responder = new Responder(setSessionIDResult, onFault);
connection.call("ServerService.setSessionID", amfResponder , user_id);
}
private function setSessionIDResult(result:Object):void {
id = result.id;
// here you'd do something to notify that the data has been downloaded. I'll usually
// use a custom Event class that just notifies that the data is ready,but I'd store
// it here in the class with the AMF call to keep all my data in one place.
}
private function onFault(fault:Object):void {
trace("AMFPHP error: "+fault);
}
I hope that can point you in the right direction.
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
This code is never going to get you what you want. You need to use a proper result function. The anonymous function responder return value will not be used by the surrounding function. It will always return 0 in this case. You are dealing with an asynchronous call here, and your logic needs to handle that accordingly.
private function pro():void {
gateway.connect('http://10.0.2.2:5000/gateway');
var responder:Responder = new Responder(handleResponse);
gateway.call('sx.xj', responder);
}
private function handleResponse(result:*):void
{
var event:MyCustomNotificationEvent = new MyCustomNotificationEvent(
MyCustomNotificationEvent.RESULTS_RECEIVED, result);
dispatchEvent(event);
//a listener responds to this and does work on your result
//or maybe here you add the result to an array, or some other
//mechanism
}
The point there being using anon functions/closures isn't going to give you some sort of pseudo-syncronous behavior.