Выяснить значение this
в JavaScript может быть непросто, вот как это сделать…
JavaScript является this
многих шуток, и это потому, что он довольно сложен. Однако я видел, как разработчики делали гораздо более сложные и специфичные для предметной области вещи, чтобы избежать этой this
. Если вы не уверены в this
, надеюсь, это поможет. Это мое this
.
Я начну с самой конкретной ситуации и закончу наименее конкретной. Эта статья похожа на большое if (…) … else if () … else if (…) …
, так что вы можете сразу перейти к первому разделу, который соответствует коду, который вы просматриваете.
- Если функция определена как стрелочная функция
- В противном случае, если функция/класс вызывается с
new
- В противном случае, если функция имеет «привязку»,
this
значение - В противном случае, если
this
установлено во время вызова - В противном случае, если функция вызывается через родительский объект (
parent.func()
) - В противном случае, если функция или родительская область находятся в строгом режиме
- В противном случае
Если функция определена как стрелочная функция:
const arrowFunction = () => {
console.log(this);
};
В этом случае значение this
всегда такое же this
и в родительской области:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Стрелочные функции хороши тем, что внутреннее значение this
нельзя изменить, оно всегда такое же, как и внешнее this
.
Другие примеры
При использовании стрелочных функций значение this
не может быть изменено с помощью bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
При использовании стрелочных функций значение this
нельзя изменить с помощью call
или apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
При использовании стрелочных функций значение this
нельзя изменить, вызвав функцию как член другого объекта:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
При использовании стрелочных функций значение this
нельзя изменить, вызвав функцию в качестве конструктора:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
«Связанные» методы экземпляра
При использовании методов экземпляра, если вы хотите, чтобы this
всегда ссылался на экземпляр класса, лучше всего использовать стрелочные функции и поля класса :
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Этот шаблон действительно полезен при использовании методов экземпляра в качестве прослушивателей событий в компонентах (таких как компоненты React или веб-компоненты).
Может показаться, что вышеизложенное нарушает правило « this
будет то же самое, что и this
в родительской области», но это начинает иметь смысл, если вы думаете о полях класса как о синтаксическом сахаре для настройки вещей в конструкторе:
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);
};
}
}
Альтернативные шаблоны включают привязку существующей функции в конструкторе или назначение функции в конструкторе. Если по какой-то причине вы не можете использовать поля класса, разумной альтернативой является назначение функций в конструкторе:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
В противном случае, если функция/класс вызывается с помощью new
:
new Whatever();
Вышеупомянутое вызовет Whatever
(или его функцию-конструктор, если это класс) с this
набором результатов Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
То же самое справедливо и для конструкторов старого стиля:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Другие примеры
При вызове с помощью new
значение this
не может быть изменено с помощью bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
При вызове с new
значение this
нельзя изменить, вызвав функцию как член другого объекта:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
В противном случае, если функция имеет «привязку» this
значению:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Всякий раз, когда boundFunction
, this
значение будет объектом, передаваемым в bind
( boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Другие примеры
При вызове связанной функции this
значение нельзя изменить с помощью call
или apply
:
// 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
значение нельзя изменить, вызвав функцию как член другого объекта:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
В противном случае, если this
установлено во время вызова:
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
.
К сожалению, такие вещи, как прослушиватели событий DOM, устанавливают для this
другое значение, и его использование может привести к созданию сложного для понимания кода:
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
в случаях, подобных приведенным выше, и вместо этого:
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()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
В этом случае функция вызывается как член obj
, поэтому this
будет obj
. Это происходит во время вызова, поэтому ссылка разрывается, если функция вызывается без родительского объекта или с другим родительским объектом:
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
имеет значение false, поскольку someMethod
не вызывается как член obj
. Возможно, вы столкнулись с этой ошибкой, когда пытались сделать что-то вроде этого:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Это не работает, потому что реализация querySelector
сама просматривает this
значение и ожидает, что это будет своего рода узел DOM, и приведенное выше разрывает это соединение. Чтобы правильно достичь вышеуказанного:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Интересный факт: не все API используют this
внутри себя. Консольные методы, такие как console.log
были изменены, чтобы избежать this
ссылок, поэтому log
не нужно привязывать к console
.
В противном случае, если функция или родительская область находятся в строгом режиме:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
В данном случае значение this
не определено. 'use strict'
не требуется в функции, если родительская область находится в строгом режиме (и все модули находятся в строгом режиме).
В противном случае:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
В данном случае значение this
такое же, как и globalThis
.
Уф!
И все! Это все, что я знаю об this
. Есть вопросы? Что-то я пропустил? Не стесняйтесь писать мне в Твиттере .
Спасибо Матиасу Биненсу , Ингвару Степаняну и Томасу Штайнеру за рецензирование.