Книга: Выразительный JavaScript

this и его область видимости

this и его область видимости

В конструкторе World есть вызов forEach. Хочу отметить, что внутри функции, передаваемой в forEach, мы уже не находимся непосредственно в области видимости конструктора. Каждый вызов функции получает своё пространство имён, поэтому this внутри неё уже не ссылается на создаваемый объект, на который ссылается this снаружи функции. И вообще, если функция вызывается не как метод, this будет относиться к глобальному объекту.

Значит, мы не можем писать this.grid для доступа к сетке изнутри цикла. Вместо этого внешняя функция создаёт локальную переменную grid, через которую внутренняя функция получает доступ к сетке.

Это промах в дизайне JavaScript. К счастью, в следующей версии есть решение этой проблемы. А пока есть пути обхода. Обычно пишут var self = this и после этого работают с переменной self.

Другое решение – использовать метод bind, который позволяет привязаться к конкретному объекту this.

var test = {
  prop: 10,
  addPropTo: function(array) {
    return array.map(function(elt) {
      return this.prop + elt;
    }.bind(this));
  }
};
console.log(test.addPropTo([5]));
// ? [15]

Функция, передаваемая в map – результат привязки вызова, и посему её this привязан к первому аргументу, переданному в bind, то есть переменной this внешней функции (в которой содержится объект test).

Большинство стандартных методов высшего порядка у массивов, таких как forEach и map, принимают необязательный второй аргумент, который тоже можно использовать для передачи this при вызовах итерационной функции. Вы могли бы написать предыдущий пример чуть проще:

var test = {
  prop: 10,
  addPropTo: function(array) {
    return array.map(function(elt) {
      return this.prop + elt;
    }, this); // ? без bind
  }
};
console.log(test.addPropTo([5]));
// ? [15]

Это работает только с теми функциями высшего порядка, у которых есть такой контекстный параметр. Если нет – приходится использовать другие упомянутые подходы.

В нашей собственной функции высшего порядка мы можем включить поддержку контекстного параметра, используя метод call для вызова функции, переданной в качестве аргумента. К примеру, вот вам метод forEach для нашего типа Grid, вызывающий заданную функцию для каждого элемента сетки, который не равен null или undefined:

Grid.prototype.forEach = function(f, context) {
  for (var y = 0; y < this.height; y++) {
    for (var x = 0; x < this.width; x++) {
      var value = this.space[x + y * this.width];
      if (value != null)
        f.call(context, value, new Vector(x, y));
    }
  }
};

Оглавление книги


Генерация: 1.252. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз