In AS3, you can initialize a member variable (or constant) by calling a member function. This happens before the constructor is called. In the meantime, the 'this' keyword is perfectly accessible in the initializing member function even though the constructor function hasn't been issued yet.
This sounds like a time bomb. Can anyone comment on the above practice?
Edit :
...
private var member:Sprite = getSprite(); // called before constructor
...
private function getSprite():Sprite {
var spr:Sprite = new Sprite();
this.addChild(spr); // 'this' used before constructor
return spr;
}
As I understand it, that's fine (if not really nice and readable). What happens when new is called is:
Memory is allocated for the instance (this becomes available)
All members are initialized (either to their default or whatever is specified)
The constructor is called
new returns this
The danger lies in that you have to make sure that nothing in getSprite() requires something that is initialized in the constructor (including the parent constructor, if it's called). I would avoid it and just initialize everything in the constructor instead.
You really just can't do what you're saying. You can't possibly access a non-static method on an instance if it hasn't yet been constructed. As to Jonatan's comment about the constructor calling super, this is implicitly done if you don't put a call to super() in your constructor body it automatically occurs at the top of the method. When you construct an object in an object oriented language you're allocating the memory for all the members of the class.
If you were to say:
var myVar:MyObject;
myVar.doSomething(); //this line creates a null pointer exception because myVar is null
If instead you were to say:
var myVar:MyObject = MyObject.createInstance(); // assuming createInstance is a static method that returns an instance of MyObject
myVar.doSomething(); //assuming createInstance() didn't return null then this works
But in this second case you can't reference "this" keyword from within the static method createInstance().
If you show a full example that refutes what I'm stating then I'll run it and delete my post, but I'm pretty sure I'm right here.
Related
I totally missed the ES6 revolution and I'm returning to JavaScript after 7 years, to find a host of very strange things happening.
One in particular is the way Function.prototype.bind() handles class constructors.
Consider this:
// an ES6 class
class class1 {
constructor (p) {
this.property = p;
}
}
var class2 = class1.bind(passer_by);
var class3 = class2.bind(passer_by,3);
class2() // exception, calling a constructor like a function
class3() // idem
console.log (new class1(1)) // class1 {property: 1}
console.log (new class2(2)) // class1 {property: 2}
console.log (new class3() ) // class1 {property: 3}
// An ES5-style pseudo-class
function pseudoclass1 (p) {
this.property = p;
}
var property = 0;
var passer_by = { huh:"???" }
var pseudoclass2 = pseudoclass1.bind(passer_by);
var pseudoclass3 = pseudoclass1.bind(passer_by,3);
pseudoclass1(1); console.log (property) // 1 (this references window)
pseudoclass2(2); console.log (passer_by) // Object { huh: "???", property: 2 }
pseudoclass3() ; console.log (passer_by) // Object { huh: "???", property: 3 }
console.log (new pseudoclass1(1)) // pseudoclass1 {property: 1}
console.log (new pseudoclass2(2)) // pseudoclass1 {property: 2}
console.log (new pseudoclass3() ) // pseudoclass1 {property: 3}
Apparently class2 and class3 are identified as constructors, and class3 is a partial application of class1 that can generate instances with a fixed value of the first parameter.
On the other hand, though they can still act as (poor man's) constructors, the ES5-style functions are indeed served the value of this set by bind(), as can be seen when they act on the hapless passer_by instead of clobbering global variables as does the unbound pseudoclass1.
Obviously, all these constructors somehow access a value of this that allows them to construct an object. And yet their this are supposedly bound to another object.
So I guess there must be some mechanism at work to feed the proper this to a constructor instead of whatever parameter was passed to bind().
Now my problem is, I can find bits of lore about it here and there, even some code apparently from some version of Chrome's V8 (where the function bind() itself seems to do something special about constructors), or a discussion about a cryptic FNop function inserted in the prototype chain, and, if I may add, the occasional piece of cargo cult bu[beep]it.
But what I can't find is an explanation about what is actually going on here, a rationale as to why such a mechanism has been implemented (I mean, with the new spread operator and destructuring and whatnot, wouldn't it be possible to produce the same result (applying some arguments to a constructor) without having to put a moderately documented hack into bind()?), and its scope (it works for constructors, but are there other sorts of functions that are being fed something else than the value passed to bind() ?)
I tried to read both the 2015 and 2022 ECMA 262 specifications, but had to stop when my brains started leaking out of my ears. I traced back the call stack as:
19.2.3.2
9.4.1.3
9.4.1.2
7.3.13
where something is said about constructors, in a way: "If newTarget is not passed, this operation is equivalent to: new F(...argumentsList)". Aha. So this pseudo-recursive call should allow to emulate a new somehow... Erf...
I'd be grateful if some kind and savvy soul could give me a better idea of what is going on, show me which part(s) of the ECMA specs deal with this mechanism, or more generally point me in the right direction.
I'm tired of banging my head against a wall, truth be told. This bit of Chrome code that seems to indicate bind() is doing something special for constructors is just incomprehensible for me. So I would at least like an explanation about it, if everything else fails.
This doesn't have anything to do with classes specifically but with how .bind works.
You have been on the right track. The most relevan section here is 9.4.1.2.
Just as a recap: ECMAScript distinguishes between two types of functions: callable functions and constructable functions. Function expressions/declarations are both, whereas e.g. class constructors are only constructable and arrow functions are only callable.
In the specification this is represented by function's internal [[Call]] and [[Construct]] methods.
new will trigger the invocation of the internal [[Construct]] method.
.bind will return a new function object with different implementations for [[Call]] and [[Construct]]. So what does the "bound" version of [[Construct]] look like?
9.4.1.2 [[Construct]] ( argumentsList, newTarget )
When the [[Construct]] internal method of a bound function exotic object, F that was created using the bind function is called with a list of arguments argumentsList and newTarget, the following steps are taken:
Let target be F.[[BoundTargetFunction]].
Assert: IsConstructor(target) is true.
Let boundArgs be F.[[BoundArguments]].
Let args be a new list containing the same values as the list boundArgs in the same order followed by the same values as the list argumentsList in the same order.
If SameValue(F, newTarget) is true, set newTarget to target.
Return ? Construct(target, args, newTarget).
What this means is that the bound constructor will "construct" the original function (F.[[BoundTargetFunction]]) with the bound arguments (F.[[BoundArguments]]) and the passed in arguments (argumentsList), but it completely ignores the bound this value (which would be F.[[BoundThis]]).
but are there other sorts of functions that are being fed something else than the value passed to bind() ?
Yes, arrow functions. Arrow functions do not have their own this binding (the value from the closest this providing environment is used instead), so bound arrow functions also ignore the bound this value.
In this example:
public function Roulette() {
new QuickLoad(url, function (o:*):void {trace(this);});
}
when QuickLoad instance does its stuff, it calls the anonymous function. One would think that this is Roulette. But no, it turns out to be the anonymous function's caller, which is QuickLoad.
This is weird to say the least, say how am I supposed to pass the "correct" this (i.e. Roulette instance) inside the anonymous function if I don't do it the normal way?
Just save the outer this instance under a different name so that it is preserved:
public function Roulette() {
var rouletteThis = this;
new QuickLoad(url, function (o:*):void {trace(rouletteThis);});
}
There is a way to call a function with an alternate this pointer, but since your function is called from within new QuickLoad(), you need to alter that call statement, and pass your this as Roulette into the constructor. Your new QuickLoad object is unaware of its surroundings, and even the caller of the constructor is unknown to it. Thus, you need to make it aware, pass a this pointer from Roulette() to QuickLoad(), AND call the function from QuickLoad with passing an alternate this pointer.
public function QuickLoad(url:String,caller:Object=null,callback:Function=null) {
// initialization code
if (callback!=null) {
if (caller!=null) callback.apply(caller,[o]);
else callback.apply(this,[o]);
}
}
...
public function Roulette() {
new QuickLoad(url, this, function (o:*):void {trace(this);});
}
Function::apply() manual.
You can also use call() method, if your argument array has fixed length. callback.call(caller,o);
Generally, in this context, this refers to an object. To quote a rather infamous acronym: INABIAF (It's not a bug, it's a feature), LOL. So, yes, the object instance QuickLoad that is calling the function is going to be what this looks at by default.
There is an exception I know of (out of many, I'm sure)...you can get anything...variable, function, object, whatever, via this["Name of Object"]. But that's an aside.
There ARE other workarounds, I'm sure, which may or may not be practical for your purposes. This is one way of passing a function, out of many, and it's the one I use the most.
Functions do not have instances. They're not objects. If you want to send a function as an argument to another function, you simply pass it, as follows in this rather weird example.
//This function accepts a function as an argument.
function bridgeOfQuestions(person:String, response:Function):void
{
if(person == "King Arthur")
{
response("What is the average airspeed velocity of an unladen swallow?");
}
else
{
response("What is your favorite color?");
}
}
//This is the function we're going to pass.
function askQuestion(question:String):void
{
trace(question);
}
//Here, we call bridgeOfQuestions and pass it the askQuestion function.
//NOTE: Leave off the parenthesis on the function being passed!
bridgeOfQuestions("Sir Lancelot", askQuestion);
bridgeOfQuestions("King Arthur", askQuestion);
EDIT: If it is just the name you're passing, a function is a function permanently. It doesn't change, unlike an object, and as I said, it doesn't have instances. Therefore, if you merely want to print out the name of the function, you'd only use trace("Roulette").
I would like to call a function name from inside an addEventListener dynamically based on function parameter.
calling with newMod("moduleA", "A"); however I am getting error TypeError: Error #1006: value is not a function.
Any suggestions on how I can call this function dynamically. I have seen some answers around using an instance[function]() but am not sure how that applies with the listener,
public function newMod(mdLd,evtTyp,param):void {
info = ModuleManager.getModule(mdLd);
var mevth:String = ("modEventHandler"+(evtTyp));
info.addEventListener(ModuleEvent.READY, function(e:ModuleEvent){
this[mevth](e, param)});
info.load(null, null, null, moduleFactory);
}
private function modEventHandlerA(e:ModuleEvent):void {
vg1.addElement(info.factory.create() as IVisualElement);
}
[EDIT]
looks like changing the call to this[mevth]() works, but I cant seem to pass additional params as needed i.e. this[mevth](parm), any suggestions welcome.
I have also updated the listener to include a function call but still no joy
When you create closure "this" doesn't point to real instance "this". You can write something like this
public function newMod(mdLd,evtTyp,param):void {
info = ModuleManager.getModule(mdLd);
var self:Object = this;
var mevth:String = ("modEventHandler"+(evtTyp));
info.addEventListener(ModuleEvent.READY, function(e:ModuleEvent){
self[mevth](e, param)});
info.load(null, null, null, moduleFactory);
}
...
But I really don't recommend you write code like this.
To awnser your comment:
Function closure means that a function remembers the context in which it has been created. In your code the following line creates a closure:
info.addEventListener(ModuleEvent.READY, function(e:ModuleEvent){
this[mevth](e, param)});
The event handler function is created in the context of the class that contains the method public function newMod(mdLd,evtTyp,param):void, so your handler has access to all members of the class. This includes variables declared in the surrounding method, the private variables/methods of the class and the protected variables/method in the whole inheritence chain of the class.
The problem with the this as Ivan Dyachenko wrote is that your handler function actually is an instance of the top-level class Function. So, if you try to call a method on this in a Function object it will be the same as calling a method in any other object - this refers to the object. In your case this will be the Function object. But your function doesn't have the method you want to call on it.
Additionally you will run into another problem with your code. Because you create the event handler inline - directly as argument of addEventListener() you will be unable to remove the event listener later. You should either use the useWeakReference parameter on addEventListener() as described here or store the handler function in a variable to hold a reference on it to remove it later with removeEventListener().
In as3 there is a flexible way to change object instance, when calling it.
call or apply members of Function object can be called with specific first arg, and reference say us, that this first arg will be "this" pointer inside function. But i've found it wrong.
I'v write little test, listed below.
public class Test
{
private var name:String = "default";
public var test3:Function = test;
public var test2:Function = function()
{
trace(this.name);
}
public function Test(name:String)
{
this.name = name;
}
public function test():void
{
trace(this.name);
}
}
and tested it.
var tmp:Test = new Test("default");
tmp.test(); //out default
tmp.test.call(new Test("new")); //out default
tmp.test2(); //out default
tmp.test2.call(new Test("new2")); //out new2
tmp.test3(); //out default
tmp.test3.call(new Test("new3")); //out default
So, in anonymous function call we can get right output, but not in case of member function.
maybe it's becouse of ambiguous "this" pointer, that should reffer real object instance for correct work, maybe smth else. I dont now, and as3 reference didnt't describe smth about it.
Finally list of questions:
Why so? By me, it's very strange, and looks like undefined behaviour;
How i can achieve that functionality? How to deceive test function like anonymous one? Isn't it call methode target?
It isn't very important, but I'll be glad any good answer. Thanks!
P.S. sorry for my English.
//EDITED: added this statement to all "name" references. Nothing changes.
When invoking the [[Call]] property, the behavior is different for
different types of closures. A closure is an object that contains a
reference to a method, and the [[Call]] property acts differently
depending on whether it is a function, method, or class closure. A
function closure is one that is of a global method that isn't
associated with any instance of a class. A method closure contains an
instance method of a class, and will always remember its original
"this" value.
If the closure is a function closure, then the first argument passed
to [[Call]] is passed on to the method and gets used as the "this"
value. If the first argument is null or undefined, then the global
object will be used as the "this" value for the method.
If the closure is a method closure, then the first argument of
[[Call]] will be ignored, and the saved "this" value for the method
closure will be passed to the method as the first argument. A method
closure records what its original "this" value was and always uses
that instead of the first argument to [[Call]].
If the closure is a class closure, and there is 1 argument passed to
[[Call]] (in addition to the "this" argument), then the call is
treated as a type conversion, and the argument will be coerced to the
type represented by the closure.
http://learn.adobe.com/wiki/display/AVM2/2.4+Method+invocation+notes
long shot: is it possible to get the name of a calling function or the constructor from the called function? is it possible to determine the previous function of the thread?
i would like to call some setter functions from my constructor and have my setter functions determine if it was the constructor that called them.
currently, i'm setting a boolean for this functionality, but perhaps there is another way?
public function Constructor(myNumber:Number)
{
this.myNumber = myNumber;
}
public function set myNumber(value:Number):void
{
myNumberProperty = value;
//if constructor called this, return;
//else do some other stuff;
}
Quote from liveDocs:
Unlike previous versions of ActionScript, ActionScript 3.0 has no arguments.caller property. To get a reference to the function that called the current function, you must pass a reference to that function as an argument. An example of this technique can be found in the example for arguments.callee.
It was in AS2.0... It unfortunately throws an error if done in AS3.0.
Technically, you should be able to do this by generating an error and getting its stack trace. The constructor will have to be on that stack trace.
try
{
throw new Error();
}
catch (e:Error)
{
// parse this for the constructor name
trace(e.getStackTrace());
}
That would be for detecting where a function call came from...
I would still go for your solution (setting the flag), as it's more oop and probably far faster in terms of performance.