Вопрос по oop, callback, prototype, this, javascript – Javascript ООП - потерял это в асинхронном обратном вызове

7

У меня есть проблема, которая до сих пор беспокоит меня - я уверен, что я делаю это плохо, но я не могу понять, как это сделать правильно.

Например, у меня есть этот код

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        this.setToken(token);
        this.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}

Проблема в том, что я не могу получить доступ к функции setToken из контекста «request.onloadend» Функция - вероятно, потому что я потерял ссылку на "это".

Какое решение этой проблемы? Могу ли я как-нибудь передать «это» вар к контексту этой функции?

Спасибо!

Ваш Ответ

6   ответов
0

this в локальной переменной за пределами обратного вызова.

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();
    var _this = this;

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        _this.setToken(token);
        _this.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}
2

.bind функция:

    var request = new XMLHttpRequest();

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        this.setToken(token);
        this.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }.bind(this); //<-- bound
}
0

XMLHTTPRequest объект в качестве контекста (то есть значениеthis). Вам нужно дать вашему экземпляру другое имя, чтобы вы могли получить к нему доступ в рамках обратного вызова:

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest(),
       , authInstance = this;

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        authInstance.setToken(token);
        authInstance.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}

Посмотрите этот ответ на другой вопрос для более подробного объяснения, почему это необходимо, я использовалauthInstance скорее, чемselfпотому что я думаю, что в целом хорошо использовать описательные имена переменных; вам никогда не придется решать, чтоauthInstance значит, тогда какself может быть неоднозначным, когда кто-то в будущем (возможно, вы!) читает код.

Другой вариант заключается в использованииbind, но это, вероятно, сложнее, чем необходимо здесь.

@Esailija Я думаю, чтоbind форма менее интуитивна и разборчива. Контекст становится понятным только в конце функции, а функция довольно длинная. Если бы это была функция из двух строк, я бы с вами согласился.
Внутри классаthis ссылаясь на текущий экземпляр класса (no matter how the binding is implemented), на мой взгляд, более интуитивно понятен. Но мы можем не согласиться с этим.
Изменение большей части кода и использование 2 отдельных «ключевых слов» а также дополнительная переменная менее сложна, чем просто бросать.bind(this) в конце?
1

this до обратного вызова:

Auth.prototype.auth = function () {
    var self = this;

    var request = new XMLHttpRequest();

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        self.setToken(token);
        self.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}
5

опию нужного вам значения:

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();
    var self = this; // save "this" value

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        self.setToken(token); // use saved "this" value
        self.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}

Другой способ заключается в использованииbind:

request.onloadend = (function () {
  var response = JSON.parse(request.responseText);

  console.log(response);
  if(response.result == 'found') {
    var token = response.token;

    this.setToken(token); // use saved "this" value
    this.isSigned = true;
  } else {
    console.log('Not logged yet.');
  }
}).bind(this);

Второй подход - «чище», но у него есть проблемы с совместимостью с браузерами (IE & lt; 9 не поддерживает его).

@Esailija: Точка :)
@Esailija: Я тоже, но, к сожалению, его практическая ценность ограничена совместимостью браузера.
Спорный вопрос сXHR.onloaded
Я думаю, что другой более прямой, код минимально изменен, и вам не нужно решать, какое псевдо-ключевое слово использовать. Просто говорю.
1

я использовал идентификаторselfОднако, пожалуйста, не стесняйтесь придавать имени более смысловой смысл:

var self = this;
request.onloadend = function () {
  ...
  self.setToken(token);
  ...
};

Похожие вопросы