В огромном мире JavaScript ключевое слово this часто ставит разработчиков в тупик, как новичков, так и опытных профессионалов.

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

Основы «этого»

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

Вот разбивка типичных правил для определения того, на что ссылается this:

Привязка по умолчанию

В наиболее распространенном случае стандартный вызов функции thisссылается на глобальный объект.В браузере глобальным объектом являетсяwindow. Однако в строгом режиме это значение остается равным undefined.

function demoFunction() {     
    console.log(this); 
}  

demoFunction(); // logs: Window {...} 
// (or `undefined` if in Strict Mode*)
// strict mode forces developer to be explicit about 
// the context in which they are calling a function, hence undefined

Неявное связывание

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

const obj = {
    name: 'John',
    sayName: function() {
        console.log(this.name);
    }
};

obj.sayName(); // logs: "John"

Явная привязка

Вы можете установить значение this явно с помощью call(), apply() или bind(). Эти методы позволяют вам напрямую указать, каким должен быть this.

function sayName() {
    console.log(this.name);
}

const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };

sayName.call(person1); // logs: "Alice"
sayName.call(person2); // logs: "Bob"

Новая привязка

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

function Person(name) {
    this.name = name;
}

const alice = new Person('Alice');
console.log(alice.name); // logs: "Alice"

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

Стрелочные функции в ES6 не имеют собственной привязки this.
Вместо этого они наследуют this от объемлющей (внешней) лексической области видимости.

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

function outerFunction() {
    this.value = 42;
    return () => console.log(this.value);
}

const obj = {
    value: 99,
    myFunction: outerFunction()
};

obj.myFunction(); // logs: 42, not 99javascriptCopy code

Слушатели событий

Внутри прослушивателей событий this обычно относится к элементу, к которому было подключено событие.

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

button.addEventListener('click', function() {
    console.log(this); // logs: <button>...</button>
});

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

В частности, поведение this отличается от традиционной функции по отношению к стрелочной функции, поэтому давайте углубимся в это и сразу поймем различия, чтобы

Традиционная функция против стрелочных функций

Традиционные функции и функции со стрелками обрабатывают ключевое слово `this` по-разному.

В традиционных функциях значение this может быть непредсказуемым в зависимости от того, как вызывается функция (как метод, конструктор, простой вызов функции и т. д.). Такое поведение является частым источником ошибок.

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

Разберемся в разнице с помощью разных реализаций.

1. Простой вызов функции

Когда функция вызывается вне какого-либо конкретного контекста объекта, значение this обычно относится к глобальному объекту (window в браузерах и global в Node.js). Однако в строгом режиме this будет undefined.

Стрелочные функции захватывают this из окружающего контекста

function traditionalFunction() {
    console.log(this);
}

traditionalFunction();  
// logs: `window` in browsers (or `undefined` in strict mode)

const anotherObj = {
    method: traditionalFunction
};
anotherObj.method();  // logs: `anotherObj`
const arrowFunction = () => {
    console.log(this);
};

const anotherObj = {
    method: arrowFunction
};

anotherObj.method();  
// logs: `window` in browsers, because that's the 
// surrounding context in which `arrowFunction` was defined.

2.Обратные вызовы

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

С традиционной функцией:

function Timer() {
    this.seconds = 0;
    setInterval(function() {
        this.seconds++;
        console.log(this.seconds);
    }, 1000);
}

const timer = new Timer();  
// NaN gets logged every second, 
// because `this` inside the `setInterval` refers to `window`
// or is `undefined` in strict mode.

Использование функции стрелки

function BetterTimer() {
    this.seconds = 0;
    setInterval(() => {
        this.seconds++;
        console.log(this.seconds);
    }, 1000);
}

const betterTimer = new BetterTimer();  
// Logs increasing numbers every second
// because the arrow function captures the `this` of `BetterTimer`.

3. Распространенные ошибки

До появления стрелочных функций разработчики часто использовали обходные пути для получения правильного this

function YetAnotherTimer() {
    this.seconds = 0;
    var self = this; // workarounds
    setInterval(function() {
        self.seconds++;
        console.log(self.seconds);
    }, 1000);
}

const yetAnotherTimer = new YetAnotherTimer();  
// Logs increasing numbers every second,
// because we stored `this` in `self`.

Стрелочные функции устраняют необходимость в таком обходном пути.

function YetAnotherTimer() {
    this.seconds = 0; 
    setInterval(() => {
        this.seconds++;
        console.log(this.seconds);
    }, 1000);
}

const yetAnotherTimer = new YetAnotherTimer();

4. Функция конструктора

Когда в качестве конструктора используется традиционная функция (с ключевым словом new), this относится к вновь созданному экземпляру.

Функции со стрелками нельзя использовать в качестве конструкторов

function Person(name) {
    this.name = name;
    this.displayThis = function() {
        console.log(this);
    };
}

const john = new Person('John');
john.displayThis();  // logs the newly created instance (`john`)

// ---------------------------------->

const ArrowConstructor = () => {};

// This will throw an error.
// const instance = new ArrowConstructor();

4. Вложенные функции

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

const group = {
    title: "My Group",
    members: ["John", "Lena", "Chris"],
    showMembers: function() {
        this.members.forEach(function(member) {
            // `this` inside this function doesn't refer to `group`
            console.log(`${this.title}: ${member}`);
        });
    }
};

group.showMembers();  
// logs "undefined: John", "undefined: Lena", "undefined: Chris"

Стрелочные функции не привязывают свои собственные `this`. Они захватывают значение `this` из окружающего контекста.

const obj = {
 value: "Hello",
 show: () => {
 console.log(this.value);
 }
};

obj.show(); 
// logs: `undefined` because `this` refers to the global context.

5. Манипулирование «этим» с помощью «вызова», «применения» и «привязки»

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

вызов

Метод call вызывает функцию с заданным значением this и аргументами.

function describe(prefix, suffix) {
  console.log(prefix + this.value + suffix);
}

const objA = { value: "apple" };
const objB = { value: "banana" };

describe.call(objA, "This is an ", "."); // Outputs: "This is an apple."
describe.call(objB, "Here's a ", "!");  // Outputs: "Here's a banana!"

применять

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

function addNumbers(a, b, c) {
  return this.base + a + b + c;
}

const obj = { base: 10 };

const result = addNumbers.apply(obj, [1, 2, 3]);
console.log(result); // Outputs: 16

связывать

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

const obj = {
  value: 42,
  getValue: function() {
    return this.value;
  }
};

const unboundGetValue = obj.getValue;
console.log(unboundGetValue()); // undefined or throws error in strict mode

const boundGetValue = obj.getValue.bind(obj);
console.log(boundGetValue()); // 42

Эти методы предлагают разработчикам гибкость в зависимости от их конкретных потребностей:

  • Хочу ли я создать новую функцию с привязанным контекстом для последующего использования? Используйте .bind().
  • Хочу ли я вызвать функцию с определенным контекстом и отдельными аргументами прямо сейчас? Используйте .call().
  • Хочу ли я вызвать функцию с определенным контекстом и массивом аргументов прямо сейчас? Используйте .apply().

Эти отдельные методы обеспечивают большую гибкость при работе с функциями и контекстами их выполнения в JavaScript.

Заключение

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

Независимо от того, определяете ли вы методы для объекта, работаете с конструкторами или явно устанавливаете контекст с помощью `call`, `apply` или `bind`, твердое понимание `this`, несомненно, поднимет вашу игру на JavaScript.