Главная » Статьи » JavaScript: что такое this?

JavaScript: что такое this?

От автора: определить значение this в JavaScript может быть непросто, однако… Тhis в JavaScript — предмет многих шуток, потому что, ну, это довольно сложно. Тем не менее, я видел, как разработчики делали гораздо более сложные и зависящие от предметной области вещи, чтобы избежать ключевого слова this. Если вы не уверены, надеюсь, это поможет. Это мое руководство по this.

Если функция определена как стрелочная функция:

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);

Предупреждение: Избегайте использования bind для привязки функции к ее внешнему this. Вместо этого используйте стрелочные функции.

Не используйте bind для установки какого-либо значения this, не связанного с родительским объектом; обычно это неожиданно, и именно поэтому у this такая плохая репутация. Вместо этого рассмотрите возможность передачи значения в качестве аргумента; ето более явно и работает со стрелочными функциями.

Другие примеры

При вызове связанной функции значение 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.

Предупреждение: не используйте call/apply для установки какого-либо значения this, не связанного с родительским объектом; обычно это неожиданно, и именно поэтому у this такая плохая репутация. Вместо этого рассмотрите возможность передачи значения в качестве аргумента; ето более явно и работает со стрелочными функциями.

К сожалению, для this, использование this для слушивателей событий DOM может привести к трудному для понимания коду:

Так не надо:

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 ложно, потому что 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.

Предупреждение: не переносите функцию на объект только для того, чтобы установить какое-то значение this, не связанное с родительским объектом; обычно это неожиданно, и именно поэтому у this такая плохая репутация. Вместо этого рассмотрите возможность передачи значения в качестве аргумента; это более явно и работает со стрелочными функциями.

В противном случае, если функция или родительская область находятся в строгом режиме:

function someFunction() { 'use strict'; return this;
} // Logs `true`:
console.log(someFunction() === undefined);

В этом случае значение this не определено. ‘use strict’ не требуется в функции, если родительская область находится в строгом режиме (и все модули находятся в строгом режиме).

Предупреждение: не полагайтесь на это. Я имею в виду, что есть более простые способы получить значение undefined.

В противном случае:

function someFunction() { return this;
} // Logs `true`:
console.log(someFunction() === globalThis);

В этом случае значение this такое же, как globalThis. Большинство людей (включая меня) называют глобальный объект globalThis, но это не на 100% технически правильно. Матиас Байненс привёл свои объяснения , в том числе, почему это называется, globalThis а не просто global.

Предупреждение: избегайте использования ссылки this на глобальный объект (да, я все еще так его называю). Вместо этого используйте globalThis, что гораздо более явно.

Это все, что я знаю об this.

Автор: Jake Archibald

Источник: web.dev

Редакция: Команда webformyself.