23.05.2008

JavaScript: контекст для вложенных функций

that и this сидели на трубе…

Как известно, this внутри вложенных функций (inner functions) равен глобальному объекту (global object). Из-за этого приходится писать нелепую конструкцию var that = this; (господи, сколько же раз я написал эту строчку!)

Пример:

var test = {
   init: function () {
      this.txt = 'Test!';
      var that = this;
      window.setTimeout(function() {
         alert(that.txt);
      }, 1000);
   }
};

Как же избавиться от ненавистного that?

Для этого попробуем воспользоваться методом apply, который позволяет задать контекст (чему будет равен this) у функции. Однако, применить apply к callback-функции передаваемой в setTimeout нельзя, поскольку apply сразу выполняет функцию к которой он применен. Поэтому напишем вспомогательный метод scope.

Function.prototype.scope = function (o) {
   var fn = this;
   return function () {
      return fn.apply(o, arguments);
   };
};

Теперь перепишем наш пример, используя метод scope.

var test = {
   init: function () {
      this.txt = 'Test!';
      window.setTimeout(function () {
         alert(this.txt);
      }.scope(this), 1000);
   }
};

Ура, мы избавились от that!

Плюсы:

  • Выкинули var that = this;
  • В inner function используем «родную» переменную this вместо трижды злоебучей that
  • Мне кажется, код стал выглядеть элегантнее

Спорные моменты:

  • Экономии объема кода нет, может быть даже небольшое увеличение
  • Возможно, ухудшилась читаемость кода, поскольку, не дочитав до конца определения функции, непонятно, на что же ссылается this

Изначально хотел опубликовать эту заметку в Техногрете, но передумал — не могу с высокой трибуны рекомендовать решение, которым, возможно, сам не буду пользоваться :)

3 комментария:

DenisX комментирует...

ну вот и зря. надо было публиковать. умный поймёт и решит, как ЕМУ надо, а глупый и так ни черта не поймет :)

Алексей комментирует...

Есть такая библиотка Prototype JavaScript framework, и в ней функция bind(), делает то же самое, плюс позволяет задать часть аргументов (от 0 до всех).

Анонимный комментирует...

Ух ты, мегареспект. Будем пробовать. Я как раз отказался от злоебучего $.each, подменяющего контекст, на dean’овский forEach.