Function.apply не использует параметр thisArg - PullRequest
9 голосов
/ 30 августа 2011

Я пишу некоторый код Actionscript3, который пытается применить метод к объекту, который определяется во время выполнения.Документация AS3 для Function.apply и Function.call указывает, что первый аргумент этих функций - это объект, который будет использоваться в качестве значения this при выполнении функции.,

Однако я обнаружил, что во всех случаях, когда выполняемая функция является методом, первый параметр для применения / вызова не используется, и «this» всегда относится к исходному объекту, для которого этот метод былсвязаны.Вот пример кода и его вывод:

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);
    }
}

Вывод:

A
B
A
A

Незначительная модификация вышеприведенного кода для создания встроенной анонимной функции, которая ссылается на thisпоказывает, что правильное поведение возникает, когда функция, которая применяется / вызывается, не является связанным методом.

Я неправильно использую apply / call, когда пытаюсь использовать ее в методе?Документация AS3 специально предоставляет код для этого случая, однако:

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

Если это действительно не работает, есть ли обходной путь, кроме превращения целевых методов в функции (что, на мой взгляд, было бы довольно уродливо))

Ответы [ 4 ]

17 голосов
/ 31 августа 2011

Это не «ошибка», но документация для call и apply очень вводит в заблуждение и совсем не объясняет, что происходит.Итак, вот объяснение того, что происходит.

Methods отличаются от Functions в ActionScript.Methods определены как часть определения класса, и методы всегда связаны с этим экземпляром.См. Методы секунды по этой ссылке .Процитируем оттуда:

Методы - это функции, которые являются частью определения класса.Как только экземпляр класса создан, метод привязывается к этому экземпляру.В отличие от функции, объявленной вне класса, метод не может использоваться отдельно от экземпляра, к которому он присоединен.

Поэтому, когда вы создаете new экземпляр MyObj, все его методысвязаны с этим экземпляром.Вот почему, когда вы пытаетесь использовать call или apply, вы не видите, что this переопределяется.Подробности см. В разделе Методы привязки .

См. этот документ для объяснения объекта traits , который ActionScript использует для разрешенияметоды и используемые по причинам производительности за кулисами, вероятно, виноват.Методы that или class являются просто синтаксическим сахаром для следующего шаблона 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;
};

Тогда:

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

или даже более интересно:

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

Vs:

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

Обратите внимание, что boundWork всегда будет выполняться в контексте экземпляра, которому он принадлежит, независимо от того, что вы передаете для this с call или apply.Который, в ActionScript, это поведение именно поэтому методы класса связаны с их экземпляром.Поэтому независимо от того, где они используются, они все равно указывают на экземпляр, из которого они произошли (что делает модель событий ActionScript немного более «вменяемой»).Как только вы это поймете, то обходной путь должен стать очевидным.

Для мест, где вы хотите творить чудеса, избегайте жестко связанных методов на основе ActionScript 3 в пользу функций-прототипов.

Например, рассмотрим следующий код 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);
    };
}

Обратите внимание на разницу в объявлении между sayName и pSayName.sayName всегда будет привязан к экземпляру, для которого он был создан.pSayName - это функция, которая доступна для экземпляров MyObj, но не привязана к конкретному ее экземпляру.

Документация для call и apply технически верна, если выречь идет о прототипе functions, а не о классе methods, о котором я не думаю, что он вообще упоминает.

1 голос
/ 30 августа 2011

Вы пытались на самом деле использовать ссылку this явно, то есть:

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

Может случиться так, что когда ключевое слово * 1005 опущено, исходный экземпляр используется для поиска поля / переменной, тогда как на самом деле thisArg повторно привязывает ключевое слово this. Если это так, то это в лучшем случае тайно, но, возможно, стоит попробовать.

1 голос
/ 30 августа 2011

Ничего себе, это очень удивительно. Oo

Также проверил его на моей стороне и попытался передать параметры, и во всех случаях переданный thisArg, похоже, вообще не используется (явно кажетсякак ошибка для меня).

Я должен был использовать нечто подобное, но имел дополнительное ограничение необходимости доступа к методу без создания экземпляра объекта (что возможно в других языках, но не в AS3>.<).Поэтому я в итоге создал статические функции и вместо этого передал свой собственный «thisArg». </p>

Поэтому создание статических функций - это возможный обходной путь:

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

Не самое лучшее, так как вы, вероятно, закончитеудвоение кода, подобного следующему>. <</p>

В качестве альтернативы, если методы общедоступны, вы можете сохранить имя функции вместо функции и получить доступ к ее методу, выполнив что-то вроде:

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

Однако это ограничено в зависимости от области применения вашего метода (публичные методы могут использоваться таким образом везде, внутренние методы в том же пакете, что и ваш класс, и частные методы только внутри класса).Так что это более ограниченно, чем использование фактического экземпляра Function вашего метода, который можно использовать из любого места.

Это похоже на довольно неприятную ошибку. Надеюсь, у кого-то еще есть лучшее решение для этого.

0 голосов
/ 10 мая 2019

.call () для свойства прототипа (функции) работало нормально, «это» было так, как это было предоставлено на сайте вызова. хороший ответ. Но обратите внимание, что модификаторы доступа (общедоступные, защищенные) соблюдаются, поэтому, если вы вызываете другой класс, вы не сможете получить доступ к защищенным членам (естественно).

...