Узнайте, как ключевое слово this на самом деле работает в JavaScript.
Это то, что постоянно используется в JavaScript, но часто то, к чему оно относится, остается загадкой. В JavaScript this
работает совершенно иначе, чем в других языках программирования, и работает по-разному в зависимости от того, используете ли вы строгий режим или нет.
Если вам трудно, вы не одиноки. Давайте посмотрим, как именно работает this
, и устраним любую путаницу в отношении того, что это означает в различных контекстах.
Что такое «это» в JavaScript
this
— это ключевое слово в JavaScript, которое относится к свойству или набору свойств в определенном контексте. Контекст, в котором мы используем this, изменяет его свойства. В глобальном контексте это относится к глобальному объекту, который в браузере является окном, но globalThis
в Node.js и других реализациях JavaScript.
console.log(this); // The same as console.log(window);
Вне каких-либо функций или кода это всегда так. Однако в разных местах это означает разное.
Это в функциях в JavaScript
В функции this по-прежнему относится к глобальному объекту. Если мы ссылаемся на это в функции, по умолчанию она будет ссылаться на окно или объект globalThis
:
console.log(this); // The same as console.log(window);
function myFunction() { console.log(this); // The same as console.log(window); }
myFunction();
Однако в строгом режиме this
внутри функции не определено.
"use strict" console.log(this); // The same as console.log(window);
function myFunction() { console.log(this); // This is undefined! }
myFunction();
Решение с вызовом()
Поначалу это немного сбивает с толку, но причина этого в том, что нам нужно добавить объект this
в myFunction — JavaScript в строгом режиме не будет по умолчанию использовать его как глобальный объект. Для этого мы должны использовать call(). В приведенном ниже примере я превратил myObject в нашу переменную this
:
"use strict" console.log(this); // The same as console.log(window);
let myObject = { firstName: "John", lastName: "Doe", age: 76 } function myFunction() { console.log(this.firstName); }
myFunction.call(myObject); // this.firstName is defined as "John", so it will console log John myFunction(); // this.firstName will be undefined, and this will throw an error.
call()
запускает myFunction
и прикрепляет myObject
к ключевому слову this
. Если не использовать вызов, а просто запустить myFunction()
, то функция вернет ошибку, так как this.firstName
будет неопределенным. Вы также можете вызвать функцию с пустым this, к которому затем можно добавить данные внутри вашей функции.
Это дает нам новое пространство для определения переменных в нашем объекте this
, вместо того, чтобы загрязнять данные из глобального объекта this:
"use strict" console.log(this); // The same as console.log(window);
function myFunction() { this.firstName = 'John'; console.log(this.firstName); // This will be "John" }
myFunction.call({});
Другое поведение в строгом режиме
Как видите, поведение сильно различается в зависимости от того, используем ли мы строгий режим или нет, поэтому важно, чтобы вы выполнили некоторые тесты, прежде чем менять код между двумя режимами.
Позвоните и подайте заявку
Иногда вы можете видеть, что call()
взаимозаменяемо используется с функцией под названием apply()
. Обе эти функции очень похожи в том смысле, что обе они вызывают функцию с указанным контекстом this. Единственная разница в том, что apply()
принимает массив, если у функции есть аргументы, а call()
принимает каждый аргумент один за другим.
Например:
"use strict" let otherNumbers = { a: 10, b: 4 } function multiplyNumbers(x, y, z) { return this.a * this.b * x * y * z }
// Both will return the same result, the only difference // being that apply() uses an array for arguments. multiplyNumbers.call(otherNumbers, 1, 2, 3); multiplyNumbers.apply(otherNumbers, [ 1, 2, 3 ]);
Упрощение этого процесса с помощью bind()
Другой способ добиться поведения, аналогичного call()
, — использовать bind()
. Подобно call()
, bind()
изменяет значение this для функции, только делает это постоянно. Это означает, что вам не нужно постоянно использовать bind()
— вы используете его только один раз.
Вот пример, где мы постоянно привязываем наш объект к нашей функции, тем самым постоянно обновляя его — нам просто нужно определить его как новую функцию. В приведенном ниже примере мы определяем новую функцию с именем boundFunction
, которая является нашей myFunction
с постоянно привязанной к ней myObject
.
Таким образом, когда мы вызываем журнал консоли, он показывает «Джон». Это отличается от вызова, который необходимо использовать каждый раз, когда мы используем функцию.
"use strict" console.log(this); // The same as console.log(window);
let myObject = { firstName: "John", lastName: "Doe", age: 76 } function myFunction() { console.log(this.firstName); }
let boundFunction = myFunction.bind(myObject); // this will bind this to myObject permanently. boundFunction(); // since we used bind, this will now be set to myObject, every time we call boundFunction() - so it will return John.
Функции обозначения стрелок и это
Одной из ключевых особенностей функций нотации стрелок в JavaScript является то, что они не содержат контекста this
. Это означает, что они наследуют this
от своего родителя. Например, предположим, что мы находимся в строгом режиме и определяем как функцию стрелки, так и функцию «нормального» стиля. Для стрелочной функции будет унаследовано this
, а для другой функции this
останется неопределенным!
"use strict" console.log(this); // The same as console.log(window);
function myFunction() { console.log(this.name); // This will be "John" let myArrowFunction = () => { console.log(this.name); // This will be "John" }
let myNormalFunction = function() { console.log(this.name); // This will throw an error, since this is undefined! }
myArrowFunction(); myNormalFunction(); }
myFunction.call({ name: "John" });
Функции конструктора и это
Еще одна интересная вещь о this
заключается в том, что при использовании в функции-конструкторе (которая является функцией, использующей ключевое слово new), возврат функции-конструктора по существу перезаписывает это. Так, например, если мы запустим следующее, хотя мы установим this.name
для John, значение, возвращаемое для имени, будет Jack:
let functionA = function() { this.name = "John"; }
let functionB = function() { this.name = "John"; return { name: "Jack" } }
let runFunctionA = new functionA(); console.log(runFunctionA.name); // Returns "John"; let runFunctionB = new functionB(); console.log(runFunctionB.name); // Returns "Jack";
Это в контексте объекта
В контексте объекта использование this относится к объекту. Например, предположим, что мы запускаем функцию внутри объекта с именем obj
, который ссылается на this.aProperty
— в данном случае это ссылается на obj
:
let obj = { aProperty: 15, runFunction: function() { console.log(this.aProperty); // Refers to 15 } }
obj.runFunction(); // Will console log 15, since this refers to obj
Это также верно, если вы используете нотацию get()/set():
"use strict" let obj = { aProperty: 15, runFunction: function() { console.log(this.aProperty); // Refers to 15 }, set updateProp(division) { this.aProperty = this.aProperty / division; // this.aProperty refers to 15 console.log(this.aProperty); } }
obj.updateProp = 15; // Will divide aProperty by 15, and console log the result, i.e. 1
Использование этого с прослушивателями событий
Еще одна особенность this
в JavaScript заключается в том, что при использовании прослушивателя событий this относится к HTML-элементу, к которому было добавлено событие. В приведенном ниже примере мы добавляем событие клика в HTML-тег с идентификатором «hello-world»:
document.getElementById('hello-world').addEventListener('click', function(e) {
console.log(this);
});
Если мы затем нажмем на наш HTML-элемент #hello-world
, мы увидим это в нашем журнале консоли:
<div id="hello-world"></div>
Использование этого с классами
В этом разделе стоит отметить, что классы в JavaScript — это просто функции внутри. Это означает, что многие функциональные возможности, которые мы видели в функциях, применимы и к классам.
По умолчанию класс будет иметь этот набор для самого экземпляра класса. В приведенном ниже примере мы можем увидеть это в действии — и runClass.name
, и runClass.whatsMyName
возвращают John.
class myClass { whatsMyName() { return this.name; } get name() { return "John"; } }
const runClass = new myClass(); console.log(runClass.name); // Returns "John" console.log(runClass.whatsMyName); // Returns "John"
Единственным исключением является то, что сюда не добавляются статические элементы. Поэтому, если мы определим функцию с ключевым словом static перед ней, она не будет находиться на this
:
class myClass { getMyAge() { return this.whatsMyAge(); } static whatsMyAge() { return this.age; } get name() { return "John"; } get age() { return 143 } }
const runClass = new myClass(); console.log(runClass.whatsMyAge()); // Throws an error, since runClass.whatsMyAge() is undefined console.log(runClass.getMyAge()); // Throws an error, since this.whatsMyAge() is undefined
Стоит отметить, что классы по умолчанию всегда находятся в строгом режиме, поэтому это будет вести себя так же, как и для строгих функций по умолчанию в классах.
Заключение
В JavaScript это может означать разные вещи. В этой статье мы рассмотрели, что это значит в разных контекстах — функциях, классах и объектах. Мы рассмотрели, как использовать bind()
, call()
и apply()
для добавления другого контекста this
к вашим функциям.
Мы также рассмотрели, как использовать this
в строгом режиме по сравнению с нестрогим режимом. После этого, я надеюсь, this
немного демистифицируется.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.