В Javascript, почему оператор "this" несовместим? - PullRequest
26 голосов
/ 17 сентября 2008

В JavaScript оператор this может ссылаться на разные вещи в разных сценариях.

Обычно в методе в «объекте» JavaScript он ссылается на текущий объект.

Но при использовании в качестве обратного вызова он становится ссылкой на вызывающий объект.

Я обнаружил, что это вызывает проблемы в коде, потому что если вы используете метод в «объекте» JavaScript в качестве функции обратного вызова, вы не сможете определить, относится ли «this» к текущему «объекту» или «this» ссылается на вызывающий объект.

Может ли кто-нибудь разъяснить, как и как обойти эту проблему?

   function TestObject() {
            TestObject.prototype.firstMethod = function(){
                      this.callback();
                      YAHOO.util.Connect.asyncRequest(method, uri, callBack);

            }

            TestObject.prototype.callBack = function(o){
              // do something with "this"
              //when method is called directly, "this" resolves to the current object
              //when invoked by the asyncRequest callback, "this" is not the current object
              //what design patterns can make this consistent?
              this.secondMethod();
            }
            TestObject.prototype.secondMethod = function() {
             alert('test');
            }
        }

Ответы [ 8 ]

82 голосов
/ 17 сентября 2008

Быстрый совет по передовым методам, прежде чем я буду болтать о волшебной этой переменной. Если вы хотите объектно-ориентированное программирование (ООП) в Javascript, которое близко отражает более традиционные / классические шаблоны наследования, выберите фреймворк, изучите его причуды и не пытайтесь стать умными. Если вы хотите стать умным, изучите javascript как функциональный язык и избегайте думать о таких вещах, как классы.

Что поднимает одну из самых важных вещей, о которых нужно помнить о Javascript, и повторять для себя, когда это не имеет смысла. Javascript не имеет классов. Если что-то похоже на класс, это умный трюк. Javascript имеет объектов (без кавычек не требуется) и функций . (это не на 100% точно, функции - это просто объекты, но иногда полезно думать о них как об отдельных вещах)

Переменная this присоединена к функциям. Всякий раз, когда вы вызываете функцию, этому присваивается определенное значение, в зависимости от того, как вы вызываете функцию. Это часто называют шаблоном вызова.

Существует четыре способа вызова функций в javascript. Вы можете вызвать функцию как метод , как функция , как конструктор и с помощью apply .

как метод

Метод - это функция, которая прикреплена к объекту

var foo = {};
foo.someMethod = function(){
    alert(this);
}

При вызове в качестве метода это будет связано с объектом, частью которого является функция / метод. В этом примере это будет связано с foo.

как функция

Если у вас есть отдельная функция, переменная this будет привязана к «глобальному» объекту, почти всегда объекту window в контексте браузера.

 var foo = function(){
    alert(this);
 }
 foo();

Это может быть тем, что сбивает вас с толку , но не расстраивайтесь. Многие считают это плохим дизайнерским решением. Поскольку обратный вызов вызывается как функция, а не как метод, поэтому вы видите то, что выглядит как непоследовательное поведение.

Многие люди справляются с этой проблемой, делая что-то вроде этого

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Вы определяете переменную , которая , которая указывает на this . Закрытие (тема, которой он владеет) сохраняет that, поэтому, если вы вызываете bar в качестве обратного вызова, оно все равно имеет ссылку.

как конструктор

Вы также можете вызвать функцию в качестве конструктора. Исходя из соглашения об именах, которое вы используете (TestObject), это также может быть тем, что вы делаете, и тем, что вас сбивает с толку .

Вы вызываете функцию как конструктор с ключевым словом new.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

При вызове в качестве конструктора будет создан новый Объект, и этот будет привязан к этому объекту. Опять же, если у вас есть внутренние функции и они используются в качестве обратных вызовов, вы будете вызывать их как функции, и this будет привязано к глобальному объекту. Используйте этот var that = this; трюк / шаблон.

Некоторые люди думают, что ключевое слово constructor / new - это кость, брошенная Java / традиционным программистам ООП как способ создания чего-то похожего на классы.

Методом применения.

Наконец, у каждой функции есть метод (да, функции - это объекты в Javascript) с именем apply. Применить позволяет вам определить значение this , а также позволяет передать массив аргументов. Вот бесполезный пример.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
12 голосов
/ 17 сентября 2008

В JavaScript this всегда относится к объекту, вызывающему выполняемую функцию. Поэтому, если функция используется в качестве обработчика события, this будет ссылаться на узел, который запустил событие. Но если у вас есть объект и вы вызываете функцию, например:

myObject.myFunction();

Тогда this внутри myFunction будет означать myObject. Имеет ли это смысл?

Чтобы обойти это, вам нужно использовать замыкания. Вы можете изменить свой код следующим образом:

function TestObject() {
    TestObject.prototype.firstMethod = function(){
        this.callback();
        YAHOO.util.Connect.asyncRequest(method, uri, callBack);
    }            

    var that = this;
    TestObject.prototype.callBack = function(o){
        that.secondMethod();
    }

    TestObject.prototype.secondMethod = function() {
         alert('test');
    }
}
3 голосов
/ 17 сентября 2008

this соответствует контексту для вызова функции. Для функций, не вызываемых как часть объекта (без оператора .), это глобальный контекст (window на веб-страницах). Для функций, вызываемых как методы объекта (через оператор.), Это объект.

Но вы можете сделать это как хотите. Все функции имеют методы .call () и .apply (), которые можно использовать для их вызова с пользовательским контекстом. Так что, если я настрою объект Чили так:

var Chile = { name: 'booga', stuff: function() { console.log(this.name); } };

... и вызовите Chile.stuff (), он даст очевидный результат:

booga

Но если я захочу, я могу взять и действительно ввернуть его :

Chile.stuff.apply({ name: 'supercalifragilistic' });

Это на самом деле весьма полезно ...

1 голос
/ 17 сентября 2008

Если вы используете фреймворк javascript, возможно, есть удобный способ справиться с этим. Например, в Prototype вы можете вызывать метод и относить его к определенному объекту «this»:

var myObject = new TestObject();
myObject.firstMethod.bind(myObject);

Примечание: bind () возвращает функцию, поэтому вы также можете использовать ее для предварительного вызова обратных вызовов внутри вашего класса:

callBack.bind(this);

http://www.prototypejs.org/api/function/bind

0 голосов
/ 17 сентября 2008

Если вы используете Prototype, вы можете использовать bind () и bindAsEventListener () для решения этой проблемы.

0 голосов
/ 17 сентября 2008

Вы также можете использовать Function.Apply ( thisArg , argsArray ) ... Где thisArg определяет значение this внутри вашей функции ... Второй параметр - это необязательный массив аргументов, который вы также можете передать своей функции.

Если вы не планируете использовать второй аргумент, не передавайте ему ничего. Internet Explorer выдаст вам TypeError, если вы передадите null (или что-либо, кроме массива) во второй аргумент function.apply () ...

Приведенный вами пример кода будет выглядеть примерно так:

YAHOO.util.Connect.asyncRequest(method, uri, callBack.Apply(this));
0 голосов
/ 17 сентября 2008

Как только методы обратного вызова вызываются из другого контекста, я обычно использую то, что я называю контекстом обратного вызова:

var ctx = function CallbackContext()
{
_callbackSender
...
}

function DoCallback(_sender, delegate, callbackFunc)
{
 ctx = _callbackSender = _sender;
 delegate();
}

function TestObject()
{
   test = function()
  {
     DoCallback(otherFunc, callbackHandler);
  }

  callbackHandler = function()
{
 ctx._callbackSender;
 //or this = ctx._callbacjHandler;
}
}
0 голосов
/ 17 сентября 2008

Я считаю, что это может быть связано с тем, как идея [замыканий] (http://en.wikipedia.org/wiki/Closure_(computer_science) работает в Javascript.

Я только начинаю справляться с замыканиями. Прочитайте связанную статью в Википедии.

Вот другая статья с дополнительной информацией.

Кто-нибудь может это подтвердить?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...