2011-08-30 14 views
9

Estoy escribiendo un código de Actionscript3 que intenta aplicar un método a un objeto que se determina en tiempo de ejecución. La documentación AS3 para Function.apply y Function.call ambos indican que el primer argumento para esas funciones es el objeto que se usará como el valor 'this' cuando se ejecuta la función.Function.apply no usa este parámetro de arg

Sin embargo, he encontrado que en todos los casos cuando la función que se ejecuta es un método, el primer parámetro para aplicar/llamar no se usa, y 'esto' siempre se refiere al objeto original al que se vinculó ese método. Aquí es un código de ejemplo y su salida:

package 
{ 
    import flash.display.Sprite;  
    public class FunctionApplyTest extends Sprite 
    { 
     public function FunctionApplyTest() 
     { 
      var objA:MyObj = new MyObj("A"); 
      var objB:MyObj = new MyObj("B"); 

      objA.sayName(); 
      objB.sayName(); 

      objA.sayName.apply(objB, []); 
      objA.sayName.call(objB); 
     } 
    } 
} 

internal class MyObj 
{ 
    private var _name:String; 
    public function MyObj(name:String) 
    { 
     _name = name; 
    } 
    public function sayName():void 
    { 
     trace(_name); 
    } 
} 

de salida:

A 
B 
A 
A 

una modificación menor para el código anterior para crear una función anónima en línea que hace referencia a 'esto' muestra que la correcta El comportamiento se produce cuando la función que se está aplicando/llamando no es un método vinculado.

¿Estoy usando aplicar/llamar incorrectamente cuando intento usarlo en un método? La documentación AS3 proporciona específicamente código para este caso, sin embargo:

myObject.myMethod.call(myOtherObject, 1, 2, 3); 

Si este es roto, ¿hay alguna solución alternativa además de hacer los métodos de destino en funciones (que sería bastante feo, en mi opinión)?

+0

Voy a tener que decir que estás haciendo algo extremadamente incorrecto si necesitas este tipo de codificación. ¿Qué estás tratando de lograr exactamente al hacer esto? –

+0

Probado en mi extremo. parece un error. Intente buscar en JIRA: http://bugs.adobe.com/jira/browse y si no lo encuentra, envíelo – divillysausages

+0

no es un error de idioma, pero la documentación mal escrita –

Respuesta

17

No es un "error", pero la documentación para call y apply es muy engañosa y no hace un buen trabajo explicando qué está pasando. Entonces aquí hay una explicación de lo que está sucediendo.

Methods son diferentes de Functions en ActionScript. Methods se definen como parte de una definición de clase, y los métodos siempre están vinculados a esa instancia. Consulte el Métodos segundo de this link. Para citar desde allí:

Los métodos son funciones que forman parte de una definición de clase. Una vez que se crea una instancia de la clase, un método está vinculado a esa instancia. A diferencia de una función declarada fuera de una clase, no se puede usar un método aparte de la instancia a la que está conectado.

Así que cuando se crea una instancia de newMyObj, todos sus métodos están obligados a esa instancia. Por eso, cuando intenta utilizar call o apply, no está viendo que this se haya anulado. Vea la sección en Bound Methods para más detalles.

Sede, this document para una explicación de los rasgos objeto, que ActionScript utiliza para resolver los métodos y se utiliza por razones de rendimiento detrás de las escenas es probablemente el culpable. Eso o métodos de clase son el azúcar solo sintáctica para el siguiente patrón de ECMAScript:

var TestClass = function(data) { 
    var self = this; 
    this.data = data; 
    this.boundWork = function() { 
     return self.constructor.prototype.unboundWork.apply(self, arguments); 
    }; 
}; 

TestClass.prototype.unboundWork = function() { 
    return this.data; 
}; 

continuación:

var a = new TestClass("a"); 
var b = new TestClass("b"); 

alert(a.boundWork()); // a 
alert(b.boundWork()); // b 

alert(a.unboundWork()); // a 
alert(b.unboundWork()); // b 

alert(a.boundWork.call(b)); // a 
alert(a.boundWork.call(undefined)); // a 

alert(a.unboundWork.call(b)); // b 

o incluso más interesante:

var method = a.unboundWork; 
method() // undefined. ACK! 

Vs:

method = a.boundWork; 
method() // a. TADA MAGIC! 

Observe que boundWork siempre se ejecutará en el contexto de la instancia a la que pertenece, pase lo que pase por this con call o apply. Lo cual, en ActionScript, este comportamiento es exactamente por qué los métodos de clase están vinculados a su instancia. Por lo tanto, no importa dónde se usen, aún apuntan a la instancia de donde provienen (lo que hace que el modelo de eventos actionscript sea un poco más "sensato"). Una vez que comprenda esto, entonces una solución alternativa debería ser obvia.

Para los lugares en los que desea hacer algo de magia, evite los métodos de encuadernación rígida basados ​​en ActionScript 3 a favor de las funciones de prototipo.

Por ejemplo, considere el siguiente código ActionScript:

package 
{ 
    import flash.display.Sprite;  
    public class FunctionApplyTest extends Sprite 
    { 
     public function FunctionApplyTest() 
     { 
      var objA:MyObj = new MyObj("A"); 
      var objB:MyObj = new MyObj("B"); 

      objA.sayName(); 
      objB.sayName(); 

      objA.sayName.apply(objB, []); // a 
      objA.sayName.call(objB); // a 

      objA.pSayName.call(objB) // b <--- 
     } 
    } 
} 

internal dynamic class MyObj 
{ 
    private var _name:String; 
    public function MyObj(name:String) 
    { 
     _name = name; 
    } 
    public function sayName():void 
    { 
     trace(_name); 
    } 

    prototype.pSayName = function():void { 
     trace(this._name); 
    }; 
} 

cuenta de la diferencia entre la declaración y sayNamepSayName. sayName siempre estará vinculado a la instancia para la que fue creado. pSayName es una función que está disponible para instancias de MyObj pero no está vinculada a una instancia particular de la misma.

La documentación para call y apply son técnicamente correcto, siempre y cuando se habla de functions prototípico y no methods clase, que no creo que se menciona en absoluto.

+0

Muchas gracias por explicar esto, ¡fue muy esclarecedor! Creo que probablemente no sea el caso de que la declaración del método sea azúcar sintáctica para su primer ejemplo de código, porque si fuera cierto, ClassType.prototype tendría propiedades para cada uno de los métodos independientes. Para mí, parece que sería lo mejor de ambos mundos, algo similar al mecanismo de Python donde objA.sayName() es azúcar sintáctica para MyObj.sayName (objA). –

1

Wow esto es muy sorprendente Oo

probado en mi lado, así y tratado de pasar en los parámetros, así como en todos los casos, no parece que el thisArg aprobado para ser utilizado en absoluto (como parece claramente un error para mí).

Tuve que usar algo similar pero tenía la restricción adicional de tener que acceder al método sin crear una instancia del objeto (que es posible en otros idiomas pero no en AS3>. <). Así que terminé creando funciones estáticas y pasé mi propio "thisArg" en su lugar.

lo tanto, hacer funciones estáticas en cambio, es una posible solución:

static public function SayName(thisArg : MyObj) : void 
{ 
    trace(thisArg._name); 
} 

No es la mejor, ya que probablemente va a terminar duplicando código como este>. <

Alternativamente, si los métodos son públicos, puede guardar el nombre de la función en lugar de la función y acceder a su método haciendo algo como:

var funcName : String = "sayName"; 
objB[funcName].apply(null, []); 
objB[funcName].call(null); 

Sin embargo, esto está limitado en función del alcance de su método (los métodos públicos se pueden usar así desde cualquier lugar, métodos internos dentro del mismo paquete que su clase y métodos privados desde dentro de la clase solamente). Por lo tanto, es más limitante que usar la instancia de función real de su método que se puede usar desde cualquier lugar.

Esto parece un error muy desagradable O.o Espero que alguien más tenga una mejor solución para esto.

1

¿Ha intentado realmente utilizar la referencia this explícitamente, es decir:

internal class MyObj 
{ 
    private var _name:String; 
    public function MyObj(name:String) 
    { 
     _name = name; 
    } 
    public function sayName():void 
    { 
     trace(this._name); 
    } 
} 

Tal vez sólo sea que cuando se omite la palabra clave this, la instancia original se utiliza para buscar el campo/variables, mientras que lo que realmente hace thisArg es volver a unir la palabra clave this. Si ese es el caso, es arcano en el mejor de los casos, pero podría valer la pena intentarlo.

+0

+1 'thisArg' hace ata 'this'. Encontré los documentos 'Array' para proporcionar algunos [ejemplos similares] (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Array.html#filter%28%29) de esto comportamiento. – NoobsArePeople2

+0

Sí, también traté de usar la palabra clave 'this' explícitamente, y lamentablemente encontré el mismo comportamiento. La documentación para Array.filter vinculada anteriormente dice explícitamente que el parámetro 'thisArg' debe ser nulo si la función de devolución de llamada es un método, por lo que parece que el implementador del filtro también se encontró con este problema. –

Cuestiones relacionadas