1

Есть код, кусочек на jquery - все хорошо работает (первая кнопка), но если кликнуть на вторую кнопку, к которой событие регистрируется при помощи addEventListener, то теряю контекст, that - это кнопка, this - объект опций ajax.

Как тогда при клике по второй кнопке вызвать _.setText() изнутри getAjax() ?

введите сюда описание изображения

$(function() {

var _ = { initJquery: function() { var that = this; $('#one').on('click', function() { that.getAjax(); }); },

initJs: function() {
  $('#two')[0].addEventListener('click', this.getAjax);

},

setText: function(text) {
  $('.update').html(text);
},

getAjax: function() {
  var that = this;
  $.ajax({
      url: 'txt.txt',
      type: 'POST',
      dataType: 'text'
    })
    .done(function(resp) {
      console.log('this');
      console.log(this);
      console.log('that');
      console.log(that);

      // при нажатии первой кнопки всё хорошо
      // тут теряю контекст при нажатии второй кнопки addEventListener
      that.setText(resp);
    })
    .fail(function() {
      console.log("error");
    });

}

};

_.initJquery(); _.initJs();

});

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input type="button" id="one" value="Кнопка 1">
<input type="button" id="two" value="Кнопка 2">
<div class="update"></div>

Результат

Всем спасибо, разобрался. Надо было так:

.done(function() {
    this.setText(resp);
}.bind(this))

Итоговый код такой:

$(function() {

  var _ = {
    initJquery: function() {
      $('#one').on('click', function() {
        this.getAjax();
      }.bind(this));
    },

    initJs: function() {
      $('#two')[0].addEventListener('click', this.getAjax.bind(this));

    },

    setText: function(text) {
      $('.update').html(text);
    },

    getAjax: function() {
      $.ajax({
          url: 'txt.txt',
          type: 'POST',
          dataType: 'text'
        })
        .done(function(resp) {
          console.log('this');
          console.log(this);
          this.setText(resp);
        }.bind(this))
        .fail(function() {
          console.log("error ajax");
        }.bind(this));

    }
  };

  _.initJquery();
  _.initJs();

});
Jean-Claude
  • 5,841
  • 2
  • 20
  • 45
  • 1
    this.getAjax.bind(this) – zhenyab Jun 16 '16 at 21:10
  • А если точнее $('#two')[0].addEventListener('click', this.getAjax.bind(this));

    Вообще, это конечно вопрос стиля и конвенций проекта, но лично я стараюсь писать как можно больше bind и стрелочных функций и как можно меньше me/self/that.

    – Утка Учится Укрываться Jun 16 '16 at 21:12
  • Bind - это полезно, но его использование не всегда возможно и оправданно. По хорошему, выбранная архитектура кода в данном случае - не удачная. Здесь бы простые функции прописать, да и вызывать их когда надо. – zhenyab Jun 16 '16 at 21:39
  • @zhenyab мне кажется bind / meselfthat - чисто вопрос договоренностей. Я писал несколько проектов на ext и несколько на backbone-based фреймворках. В ext - везде писали me/self/that, в бэкбоне везде bind. Это раз. Во вторых, в выбранном примере нет архитектуры, это очевидно минимальный воспроизводимый пример, искуственный. – Утка Учится Укрываться Jun 16 '16 at 21:44
  • Полемику разводить не буду по поводу bind, а в примере есть определенный pattern (как по-русски красивее сказать?) в общем pattern я бы заменил :) – zhenyab Jun 16 '16 at 21:50
  • да, это типа учебный код. – Jean-Claude Jun 17 '16 at 07:20
  • @Утка да, но при $('#two')[0].addEventListener('click', this.getAjax.bind(this)); работает только с var that = this; – Jean-Claude Jun 17 '16 at 07:21
  • @Jean-Claude, ну да, я не стал править весь ваш код, внес минимальные изменения. Просто сказал что вообще можно везде прибиндить, Вы можете это сами сделать. – Утка Учится Укрываться Jun 17 '16 at 07:29

1 Answers1

1

Кратко:

 $('#two')[0].addEventListener('click', this.getAjax.bind(this));

Почему у вас не работает?
Как я здесь пытался объяснить совсем недавно, функция существует сама по себе, без привязанного к ней объекта. В callback передается сама функция, как самостоятельная сущность. Информации о том, с чем ее связать не передается. То что слева от точки служит лишь для того, чтобы эту функцию найти. А this, как раз означает какой-то объект, методом которого функцию мы считаем, поэтому он и становится undefined (или window в нестрогом режиме).

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

Вообще везде где вы сохраняете контекст через var that = this можно сохранить его и через bind. Это вопрос договоренностей в конкретном проекте, но лично я предпочитаю так. А в некоторых местах вообще удобнее использовать es6 arrow functions, которые не создают своего отдельного this.

Ваша первая функция с bind

initJquery: function() {
  $('#one').on('click', function() {
    this.getAjax();
  }.bind(this));
}

Она же (почти) со стрелочками:

initJquery: function() {
        $('#one').on('click', () => this.getAjax());
}
  • arrow functions ещё не везде поддерживаются, так что лучше их пока не использовать, либо использовать скомпилированными сборщиками – Василий Барбашев Jun 17 '16 at 05:27
  • да, именно поэтому пока стрелочные трогать не буду. (ещё не везде поддерживаются) – Jean-Claude Jun 17 '16 at 07:23
  • @Jean-Claude, если у вас есть какая-то сборка, то транспилер к ней добавляется элементарно – Утка Учится Укрываться Jun 17 '16 at 07:29
  • @Утка не, пока я не умею этого. Итоговый код имеет вид http://jsbin.com/kojefilaxi/edit?html,output но без var that = this; он не работает, как убрать that в getAjax() ? – Jean-Claude Jun 17 '16 at 07:33
  • @Утка вроде разобрался: .done(function() { this.setText(resp); }.bind(this)) – Jean-Claude Jun 17 '16 at 07:40