Имеет ли функция JS ‘this’ значение по умолчанию?


Отказ от ответственности

В JS существует 1000000 статей об этом. Я думаю, что вполне нормально добавить еще одну.

Второй параметр функции Array.prototype.map

Недавно я просматривал страницу MDN о методе .map() прототипа массива и встретил один абзац, который привлек мое внимание:

где thisArg является вторым параметром метода .map() (1)

Второе предложение гласит — «В противном случае, значение undefined будет использовано как значение this.» Итак, давайте проведем быстрый тест и передадим внутри .map() только обратный вызов:

[1,2,3].map(function(){console.log(this)})
// globalObject
// globalObject
// globalObject
Вход в полноэкранный режим Выйти из полноэкранного режима

Это кажется непонятным, так как это значение является глобальным объектом, а не неопределенным, когда выполняется обратный вызов. На самом деле это ожидается, так как обратный вызов выполняется в нестрогом режиме. Почему же тогда фраза говорит о неопределенности?

Тем не менее, фраза имеет смысл, особенно если обратить внимание на строку — «The this value ultimately observable by callbackFn».

Итак, что же означает «в конечном счете наблюдаемое»?

Значение по умолчанию функции this

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

function f (){
   console.log(this)
}

f.apply(undefined)
// globalObject
Войти в полноэкранный режим Выйти из полноэкранного режима

Таким образом, можно сделать вывод, что функция f в конечном итоге наблюдает значение globalObject в этом, а не в неопределенном значении, которое мы явно передаем. На мой взгляд, такое поведение можно сравнить с параметрами функции по умолчанию:

function f(a=globalObject){
    console.log(a)
}

f();
Войти в полноэкранный режим Выйти из полноэкранного режима

Время спецификаций!

Теперь пришло время открыть спецификацию ES, чтобы убедиться, что мы не упустили ни одного случая.

Я бы очень хотел облегчить вам жизнь, поэтому существует абстрактная операция OrdinaryCallBindThis, которая отвечает за значение по умолчанию. В ней несколько шагов, но 2 из них являются основными:

Обратим внимание на 2 переменные на рисунке:

  • thisArgument — передаваемое значение в функции;
  • thisValue — конечное наблюдаемое значение функции;

Шаг 5 говорит, что если наша функция вызывается в строгом режиме, то thisValue становится thisArgument и значение по умолчанию для this неприменимо.

function f (){
   'use strict'
   console.log(this)
}

f.apply(undefined)
// undefined
Вход в полноэкранный режим Выход из полноэкранного режима

Шаг 6 говорит, что если наша функция вызывается в нестрогом режиме и thisArgument равен undefined или null, то thisValue (фактическое значение this внутри функции) является globalObject (globalEnv.[[GlobalThisValue]] на картинке — это просто глобальный объект).

function f (){
   console.log(this)
}

f.apply(undefined)
f.apply(null)
// globalObject
// globalObject
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, нулевое значение также заменяется значением по умолчанию globalObject.

Также стоит взглянуть на 6.b, который также действителен для нестрогого режима. Там вызывается операция toObject(), которая создает объекты-обертки (String, Number, etc) для примитивных значений thisArgument:

function f (){
   console.log(this)
}

f.apply('')
// String {''}
Войти в полноэкранный режим Выйти из полноэкранного режима

Поэтому значение функции this всегда является объектным типом для нестрогого режима.

Заключение

Каждый раз, когда вы вызываете функцию в JS, вы передаете в нее значение this. Даже если вы вызываете функцию типа f(), вы все равно неявно передаете неопределенное значение. A строгий режим принимает любое значение this без каких-либо модификаций. A нестрогий функция заменяет неопределенные и нулевые значения глобальным объектом, а также преобразует все примитивные значения в объекты.

Не стесняйтесь использовать этот вывод, чтобы завоевать сердце вашего следующего интервьюера.

P.S.
Есть один мой старый пост об этом значении в обратном вызове setTimeout. В обратном вызове браузера это всегда window и не зависит от строгого/нестрогого режима. Причина в том, что метод setTimeout при выполнении обратного вызова передает объект window, а не неопределенное значение. Нестрогая подстановка по умолчанию просто не работает, так как thisArgument не является ни undefined, ни null.

(1) — Метод Array.prototype.map() имеет второй необязательный параметр:

// Callback function
map(callbackFn)
map(callbackFn, thisArg)
Войти в полноэкранный режим Выйти из полноэкранного режима

этот второй параметр можно использовать вместо метода .bind() для функции обратного вызова.

Оцените статью
devanswers.ru
Добавить комментарий