При написании кода на JavaScript вы могли встретить ключевое слово this, которое относится к текущему контексту выполнения или значению текущего объекта. Это специальное ключевое слово, которое автоматически определяется в каждой функции и может использоваться для ссылки на объект, свойством которого является функция.

Привязки

Значение this определяется тем, как вызывается функция. Он может быть задан явно или неявно. Есть четыре способа вызвать функцию в JavaScript, и каждый способ устанавливает значение this по-разному:

Вызов функции (привязка по умолчанию)

Когда функция вызывается как отдельная функция, this указывает на глобальный объект. В Node.js глобальный объект называется global:

function sayHello() {
  return "Hello " + this.name;
}

sayHello();  // returns: 'Hello undefined'

global.name = "Charlie"
sayHello();  // returns: 'Hello Charlie'

Примечание. В веб-браузере глобальным объектом является объект window.

Вызов метода (неявная привязка)

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

const person = {
    name: 'Charlie',
    sayHello: function() {
        return "Hello " + this.name;
    }
}

person.sayHello();  // returns: 'Hello Charlie'

Вызов конструктора (привязка конструктора)

Когда функция вызывается с ключевым словом new, this указывает на только что созданный объект.

function Person(name) {
    this.name = name;
    this.sayHello = function() {
        return "Hello " + this.name;
    }
}
const charlie = new Person('Charlie');
charlie.sayHello();  // returns: 'Hello Charlie'

Косвенный вызов (явная привязка)

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

const person = {
    name: 'Charlie'
}

function greet(timeOfDay) {
    return `Good ${timeOfDay} ${this.name}`;
}

greet.call(person, 'Morning');  // returns: 'Good Morning Charlie'

apply работает аналогичным образом, за исключением необходимости передачи массива аргументов:

const person = {
    name: 'Charlie'
}

function greet(timeOfDay) {
    return `Good ${timeOfDay} ${this.name}`;
}

greet.apply(person, ['Morning']);  // returns: 'Good Morning Charlie'

Жесткий переплет

Также можно явно установить значение this с помощью метода bind() — это возвращает новую функцию со значением this, установленным для объекта, переданного в качестве первого аргумента.

const charlie = {
    name: 'Charlie',
    sayHello: function() {
        return "Hello " + this.name;
    }
}

charlie.sayHello();  // returns: 'Hello Charlie'

const paul = {
    name: 'Paul'
}
const sayHelloToPaul = charlie.sayHello.bind(paul);

sayHelloToPaul();   // returns: 'Hello Paul'

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

const btn = document.querySelector('button');

const person = {
  name: 'Charlie',
  greet: function() {
    console.log(`Hello ${this.name}!`);
  }
};

// Hard bind to person
btn.addEventListener('click', person.greet.bind(person));

Стрелочные функции

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

Стрелочные функции лексически связывают this с окружающим контекстом выполнения:

const charlie = {
  name: 'Charlie',
  sayHello1: function() {
    function greet(timeOfDay) {
      console.log(`Good ${timeOfDay} ${this.name}`);
    }
    greet('Morning');
  },
  sayHello2: function() {
    const greet = (timeOfDay) => {
      console.log(`Good ${timeOfDay} ${this.name}`);
    }
    greet('Morning');
  }
}

charlie.sayHello1();  // prints: 'Good Morning undefined'
charlie.sayHello2();  // prints: 'Good Morning Charlie'

Функции обратного вызова

Остерегайтесь сброса обратного вызова, даже при использовании стрелочных функций:

function greet(timeOfDay) {
  console.log(`Good ${timeOfDay} ${this.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet('Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning undefined'

Обратите внимание, что this.name внутри greet равно undefined. Вот где может пригодиться жесткая привязка:

function greet(timeOfDay) {
  console.log(`Good ${timeOfDay} ${this.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet.bind(this)('Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning Charlie'

Однако обратите внимание, что bind(this) не решает проблему, если используются стрелочные функции, поскольку для него значение this лексически связано, то есть оно сохраняет значение this из окружающей его области видимости при объявлении, в нашем случае объект global:

const greet = (timeOfDay) => {
  console.log(`Good ${timeOfDay} ${this.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet.bind(this)('Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning undefined'

Чтобы решить эту проблему, контекст должен быть передан:

const greet = (context, timeOfDay) => {
  console.log(`Good ${timeOfDay} ${context.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet(this, 'Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning Charlie'

Еда на вынос

Понимание того, как this работает в JavaScript, имеет решающее значение для написания надежного и поддерживаемого кода, особенно при работе со сложными объектно-ориентированными программами. Вот некоторые 5 основных выводов:

  1. this относится к текущему контексту выполнения, в большинстве случаев к объекту или глобальной области.
  2. this можно явно установить с помощью .call(), .apply() или .bind().
  3. Стрелочные функции используют значение this своего лексического окружающего контекста.
  4. Остерегайтесь сброса this, иногда в глобальную область, в функциях обратного вызова. Чтобы избежать этого, используйте явную привязку или стрелочные функции для сохранения значения this исходного контекста.
  5. Избегайте использования this в определенных ситуациях, когда это затрудняет чтение и поддержку нашего кода, особенно в больших приложениях.

Первоначально опубликовано на https://www.90-10.dev 25 января 2023 г.