Выяснить значение 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 . Есть вопросы? Что-то я пропустил? Не стесняйтесь писать мне в Твиттере .
Спасибо Матиасу Биненсу , Ингвару Степаняну и Томасу Штайнеру за рецензирование.