Отказ от ответственности
В 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() для функции обратного вызова.