От автора: определить значение this в JavaScript может быть непросто, однако… Тhis в JavaScript — предмет многих шуток, потому что, ну, это довольно сложно. Тем не менее, я видел, как разработчики делали гораздо более сложные и зависящие от предметной области вещи, чтобы избежать ключевого слова this. Если вы не уверены, надеюсь, это поможет. Это мое руководство по this.
Если функция определена как стрелочная функция:
1 2 3 |
const arrowFunction = () => { console.log(this); }; |
В этом случае значение this является всегда тем же, что и this в родительской области:
1 2 3 4 5 6 |
const outerThis = this; const arrowFunction = () => { // Always logs `true`: console.log(this === outerThis); }; |
Стрелочные функции хороши тем, что внутреннее значение this нельзя изменить, оно всегда такое же, как и внешнее this.
Другие примеры
При использовании стрелочных функций значение this нельзя изменить с помощью bind:
1 2 |
// Logs `true` - bound `this` value is ignored: arrowFunction.bind({foo: 'bar'})(); |
Для стрелочных функций значение this нельзя изменить с помощью call или apply:
1 2 3 4 |
// Logs `true` - called `this` value is ignored: arrowFunction.call({foo: 'bar'}); // Logs `true` - applied `this` value is ignored: arrowFunction.apply({foo: 'bar'}); |
В стрелочных функциях значение this нельзя изменить, вызвав функцию как член другого объекта:
1 2 3 |
const obj = {arrowFunction}; // Logs `true` - parent object is ignored: obj.arrowFunction(); |
В стрелочных функциях значение this нельзя изменить, вызвав функцию как конструктор:
1 2 |
// TypeError: arrowFunction is not a constructor new arrowFunction(); |
‘Связанные’ методы экземпляра
С помощью методов экземпляра, если вы хотите, чтобы this всегда ссылались на экземпляр класса, лучший способ — использовать стрелочные функции и поля класса:
1 2 3 4 5 6 |
class Whatever { someMethod = () => { // Always the instance of Whatever: console.log(this); }; } |
Этот шаблон действительно полезен при использовании методов экземпляра в качестве прослушивателей событий в компонентах (таких как компоненты React или веб-компоненты).
Вышеупомянутое может показаться нарушением правила «this будет таким же, как this в родительской области», но это имеет смысл, если вы думаете о полях класса как о синтаксическом сахаре для установки полей в конструкторе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Whatever { someMethod = (() => { const outerThis = this; return () => { // Always logs `true`: console.log(this === outerThis); }; })(); } // …is roughly equivalent to: class Whatever { constructor() { const outerThis = this; this.someMethod = () => { // Always logs `true`: console.log(this === outerThis); }; } } |
Альтернативные схемы включают привязку существующей функции в конструкторе или присвоение функции в конструкторе. Если по какой-то причине вы не можете использовать поля класса, разумной альтернативой является назначение функций в конструкторе:
1 2 3 4 5 6 7 |
class Whatever { constructor() { this.someMethod = () => { // … }; } } |
В противном случае, если функция / класс вызывается с помощью new:
1 |
new Whatever(); |
Вышеупомянутое вызовет Whatever (или его конструктор, если это класс) с установленным результатом this: Object.create(Whatever.prototype).
1 2 3 4 5 6 7 8 9 10 |
class MyClass { constructor() { console.log( this.constructor === Object.create(MyClass.prototype).constructor, ); } } // Logs `true`: new MyClass(); |
То же верно и для конструкторов такого стиля:
1 2 3 4 5 6 7 8 |
function MyClass() { console.log( this.constructor === Object.create(MyClass.prototype).constructor, ); } // Logs `true`: new MyClass(); |
Другие примеры
При вызове с помощью new, значение this нельзя изменить с помощью bind:
1 2 3 |
const BoundMyClass = MyClass.bind({foo: 'bar'}); // Logs `true` - bound `this` value is ignored: new BoundMyClass(); |
При вызове с помощью new значение this нельзя изменить, вызвав функцию как член другого объекта:
1 2 3 |
const obj = {MyClass}; // Logs `true` - parent object is ignored: new obj.MyClass(); |
В противном случае, если функция имеет «связанное» значение this:
1 2 3 4 5 6 |
function someFunction() { return this; } const boundObject = {hello: 'world'}; const boundFunction = someFunction.bind(boundObject); |
Каждый раз, когда вызывается boundFunction , значением this будет объект, переданный в bind(boundObject).
1 2 3 4 |
// Logs `false`: console.log(someFunction() === boundObject); // Logs `true`: console.log(boundFunction() === boundObject); |
Предупреждение: Избегайте использования bind для привязки функции к ее внешнему this. Вместо этого используйте стрелочные функции.
Не используйте bind для установки какого-либо значения this, не связанного с родительским объектом; обычно это неожиданно, и именно поэтому у this такая плохая репутация. Вместо этого рассмотрите возможность передачи значения в качестве аргумента; ето более явно и работает со стрелочными функциями.
Другие примеры
При вызове связанной функции значение this нельзя изменить с помощью call или apply:
1 2 3 4 |
// Logs `true` - called `this` value is ignored: console.log(boundFunction.call({foo: 'bar'}) === boundObject); // Logs `true` - applied `this` value is ignored: console.log(boundFunction.apply({foo: 'bar'}) === boundObject); |
При вызове связанной функции значение this нельзя изменить, вызвав функцию как член другого объекта:
1 2 3 |
const obj = {boundFunction}; // Logs `true` - parent object is ignored: console.log(obj.boundFunction() === boundObject); |
В противном случае, если this установлено во время вызова:
1 2 3 4 5 6 7 8 9 10 |
function someFunction() { return this; } const someObject = {hello: 'world'}; // Logs `true`: console.log(someFunction.call(someObject) === someObject); // Logs `true`: console.log(someFunction.apply(someObject) === someObject); |
Значение this — это объект, переданный в call/apply.
Предупреждение: не используйте call/apply для установки какого-либо значения this, не связанного с родительским объектом; обычно это неожиданно, и именно поэтому у this такая плохая репутация. Вместо этого рассмотрите возможность передачи значения в качестве аргумента; ето более явно и работает со стрелочными функциями.
К сожалению, для this, использование this для слушивателей событий DOM может привести к трудному для понимания коду:
Так не надо:
1 2 3 4 5 |
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); }); |
Я избегаю использования this в таких случаях, как указано выше, а вместо этого использую:
1 2 3 4 5 6 |
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); }); |
В противном случае, если функция вызывается через родительский объект (parent.func()):
1 2 3 4 5 6 7 8 |
const obj = { someMethod() { return this; }, }; // Logs `true`: console.log(obj.someMethod() === obj); |
В этом случае функция вызывается как член obj, так как this и будет obj. Это происходит во время вызова, поэтому ссылка теряется, если функция вызывается без родительского объекта или с другим родительским объектом:
1 2 3 4 5 6 7 8 9 |
const {someMethod} = obj; // Logs `false`: console.log(someMethod() === obj); const anotherObj = {someMethod}; // Logs `false`: console.log(anotherObj.someMethod() === obj); // Logs `true`: console.log(anotherObj.someMethod() === anotherObj); |
someMethod() === obj ложно, потому что someMethod не вызывается в качестве члена obj. Возможно, вы столкнулись с этим, пытаясь сделать что-то вроде этого:
1 2 3 |
const $ = document.querySelector; // TypeError: Illegal invocation const el = $('.some-element'); |
Это не работает, потому что реализация querySelector имеет свое собственное значение this и ожидает, что это будет своего рода узел DOM. Для правильной реализации вышеуказанного:
1 2 3 |
const $ = document.querySelector.bind(document); // Or: const $ = (...args) => document.querySelector(...args); |
Интересный факт: не все API используют this для внутренних целей. Такие методы консоли, как console.log были изменены, чтобы избежать ссылок на this, поэтому log не нужно связывать с console.
Предупреждение: не переносите функцию на объект только для того, чтобы установить какое-то значение this, не связанное с родительским объектом; обычно это неожиданно, и именно поэтому у this такая плохая репутация. Вместо этого рассмотрите возможность передачи значения в качестве аргумента; это более явно и работает со стрелочными функциями.
В противном случае, если функция или родительская область находятся в строгом режиме:
1 2 3 4 5 6 7 |
function someFunction() { 'use strict'; return this; } // Logs `true`: console.log(someFunction() === undefined); |
В этом случае значение this не определено. ‘use strict’ не требуется в функции, если родительская область находится в строгом режиме (и все модули находятся в строгом режиме).
Предупреждение: не полагайтесь на это. Я имею в виду, что есть более простые способы получить значение undefined.
В противном случае:
1 2 3 4 5 6 |
function someFunction() { return this; } // Logs `true`: console.log(someFunction() === globalThis); |
В этом случае значение this такое же, как globalThis. Большинство людей (включая меня) называют глобальный объект globalThis, но это не на 100% технически правильно. Матиас Байненс привёл свои объяснения , в том числе, почему это называется, globalThis а не просто global.
Предупреждение: избегайте использования ссылки this на глобальный объект (да, я все еще так его называю). Вместо этого используйте globalThis, что гораздо более явно.
Это все, что я знаю об this.
Автор: Jake Archibald
Источник: web.dev
Редакция: Команда webformyself.