Подтвердить что ты не робот

Поддержание ссылки на "this" в Javascript при использовании обратных вызовов и закрытий

Мне присваивается переменная "this", поэтому я могу легко использовать ее в обратных вызовах и закрытиях.

Это плохая практика? Есть ли лучший способ вернуться к исходной функции?

Вот типичный пример.

User.prototype.edit = function(req, res) {

  var self = this,
      db = this.app.db;

  db.User.findById('ABCD', function(err, user)) {

    // I cannot use this.foo(user)
    self.foo(user);
  });
};

User.prototype.foo = function(user) {

};

Обычно вы используете этот подход или нашли более чистое решение?

4b9b3361

Ответ 1

Существует три основных способа обработки this в обратных вызовах:

1. Создайте переменную с лексической областью, как вы сейчас делаете

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

function edit(req, res) {
    var that = this,
    db.User.findById('ABCD', function(err, user){
        that.foo(user);
    });
};

Одним из преимуществ этого подхода является то, что после преобразования кода в использование that вы можете добавить столько внутренних обратных вызовов, сколько захотите, и все они будут легко работать из-за лексического охвата. Другим преимуществом является то, что его очень простая и будет работать даже в древних браузерах.

2. Используйте метод .bind().

Функции Javascript имеют метод .bind(), который позволяет создать версию из них с фиксированным this.

function edit(req, res) {
    db.User.findById('ABCD', (function(err, user){
        this.foo(user);
    }).bind(this));
};

Когда дело доходит до обработки this, метод связывания особенно полезен для одного из обратных вызовов, когда добавление функции обертки будет более подробным:

setTimeout(this.someMethod.bind(this), 500);

var that = this;
setTimeout(function(){ that.doSomething() }, 500);

Основным недостатком bind является то, что если у вас есть вложенные обратные вызовы, вам также нужно вызвать bind на них. Кроме того, IE <= 8 и некоторые другие старые браузеры, не внедряют метод bind, поэтому вам может потребоваться использование shimming library, если вам все равно придется их поддерживать.

3. Если вам нужно более мелкомасштабное управление функциями или аргументами функции, вернитесь к .call() и .apply()

Более примитивными способами управления параметрами функции в Javascript, включая this, являются .call() и .apply(). Они позволяют вам вызывать функцию с любым объектом в качестве this и любыми значениями в качестве параметров. apply особенно полезен для реализации вариационных функций, поскольку он получает список аргументов как массив.

Например, вот версия bind, которая получает метод для привязки в виде строки. Это позволяет нам записать this только один раз, а не дважды.

function myBind(obj, funcname){
     return function(/**/){
         return obj[funcname].apply(obj, arguments);
     };
}

setTimeout(myBind(this, 'someMethod'), 500);

Ответ 2

К сожалению, это хорошо налаженный способ сделать это, хотя that - это широко распространенное соглашение об именах для this "copy".

Вы также можете попробовать:

db.User.findById('ABCD', this.foo.bind(this));

Но для этого подхода требуется foo() иметь точно такую ​​же подпись, что и ожидаемая findById() (в вашем примере вы пропускаете err).

Ответ 3

Вы можете создать прокси для обратного вызова с помощью:

var createProxy = function(fn, scope) {
  return function () {
    return fn.apply(scope, arguments);
  }; 
};

Используя это, вы можете сделать следующее:

db.User.findById('ABCD', createProxy(function(err, user)) {
  this.foo(user);
}, this));

jQuery делает что-то похожее: $. proxy

И, как отметили другие, используя bind, посмотрите здесь, если проблема совместимости:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind#Compatibility