В JavaScript this - это неотъемлемая концепция, о которой легко споткнуться. this относится к контексту выполнения функции, который устанавливается во время выполнения, а не во время определения.

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

Функции, которые принимают другие функции в качестве аргументов, возвращают функцию или и то, и другое, известны как функции высшего порядка.

Итак, почему нас волнует, что this устанавливается во время выполнения? Потому что, если мы возьмем функцию и передадим ее, не проявляя осторожности, мы столкнемся с потерей контекста, потому что значение this изменится в зависимости от того, где эта функция в конечном итоге вызывается. Ясно, как грязь, правда? Давайте посмотрим на пример:

В строке 3 мы назначаем анонимную функцию свойству getBar объекта foo. Когда мы вызываем getBar в этом контексте в строке 8, getBar является методом объекта foo, поэтому неявное значение this в строке 4 является родительским объектом foo, и код дает неудивительный результат: регистрируется значение foo.bar, которое является baz.

Теперь о хитрости: строка 10 присваивает значение foo.bar переменной qux в глобальном контексте, и делает это без вызова анонимной функции, хранящейся в foo.bar. Когда мы действительно вызываем функцию, хранящуюся в qux в строке 11, контекст выполнения теперь отличается от того, что было раньше, поэтому неявный контекст выполнения здесь является глобальным объектом. Другими словами, this теперь относится к global или window (в зависимости от того, выполняется ли этот JavaScript в узле или в браузере - здесь я буду просто говорить global).

Так почему же строка 11 записывает undefined? Потому что мы испытали потерю контекста! Мы попытались записать значение this.bar, за исключением того, что в этом контексте выполнения this.bar преобразуется в global.bar. Поскольку в настоящее время у объекта global нет свойства bar, возвращаемое значение - undefined.

Итак, как это исправить?

Вместо того, чтобы полагаться на контекст неявного выполнения, мы можем решить для себя, каким должен быть контекст выполнения (т. Е. Значение this в вызываемой функции). Мы называем это явным исполнением контекстом. Есть несколько способов добиться этого.

Здесь, во время вызова, мы используем Function.prototype методы call и apply, чтобы явно установить this объекту foo при выполнении функции. Это банальный пример, но стоит отметить, что здесь можно передать любой контекст, а не только foo. Хотя call и apply могут выглядеть как псевдонимы друг для друга, это не так. Оба метода принимают явный контекст, который должен быть установлен для их первого аргумента. Разница в том, что apply ожидает, что его следующий аргумент будет массивом, содержащим все остальные аргументы для передачи функции, в то время как call ожидает, что каждый аргумент будет передан индивидуально.

Давайте изучим эти различия и их ограничения, прежде чем искать более постоянное решение.

Здесь мы видим, что apply ожидает массив аргументов, которые он передаст функции, для которой задан контекст, в то время как call требует, чтобы каждый аргумент передавался отдельно. В строке 12 записано NaN (или «Не число»), потому что 5 * undefined === NaN. Стоит отметить, что когда мы используем и call, и apply, мы устанавливаем контекст this только для выполнения этой конкретной функции. Для более постоянного решения нам нужно использовать bind.

При постоянной привязке контекста qux в строке 10 каждый вызов qux будет иметь контекст, привязанный к foo, и больше не будет происходить потеря контекста. Теперь взгляните на строку 6. Передав второй аргумент в forEach, мы можем использовать необязательный аргумент thisArg, чтобы аналогичным образом установить контекст функции обратного вызова, переданной в forEach. Это неуклюжая техника, но я скоро покажу способ лучше. В приведенном выше примере, поскольку функция обратного вызова, переданная forEach в строке 4, не является методом объекта foo, она будет неявно выполняться как функция, а не как метод. При выполнении как функции его неявное значение this будет преобразовано в объект global, за исключением того, что мы исправили это, передав thisArg.

Из-за такого беспорядка я предпочитаю использовать стрелочные функции ES6 для достижения того же самого с гораздо более чистым синтаксисом. Стрелочные функции ES6 не изменяют контекст this. Также обратите внимание, что в качестве однострочника скобки можно не устанавливать. Скобку также можно опустить, потому что есть только один параметр функции.

Помните, что при использовании bind он будет работать только с выражением функции (т.е. функцией, хранящейся в переменной). Это не будет работать с объявлением функции. Есть и другие способы передачи контекста this функциям, вложенным в методы, например, сохранение значения this в локальной переменной, называемой чем-то вроде self, но этот подход запутан и не имеет значения из-за стрелочных функций ES6.

Резюме:

Обязательно помнить, что контекст выполнения функции, также известный как this, неявно определяется во время вызова, если он не устанавливается явно заранее. Контекст выполнения функции (this) может быть явно установлен для конкретного вызова с помощью call или apply или навсегда привязан с помощью функции bind.

Для дальнейшего чтения ознакомьтесь с документацией MDN: call, apply и bind.

Фото Стефана Блондала