Значение this в JavaScript не зависит от объекта, в котором создана функция. Оно определяется во время вызова.
Любая функция может иметь в себе this.
Совершенно неважно, объявлена она в объекте или вне него.
Значение this называется контекстом вызова и будет определено в момент вызова функции.
Например: такая функция вполне допустима:
Эта функция еще не знает, каким будет this. Это выяснится при выполнении программы.
Есть несколько правил, по которым JavaScript устанавливает this.
Вызов функции с new
При вызове функции с new, значением this является новосоздаваемый объект. Мы уже обсуждали это в разделе о создании объектов.
Вызов в контексте объекта
Самый распространенный случай — когда функция объявлена в объекте или присваивается ему, как в примере ниже:
При вызове функции как метода объекта, через точку или квадратные скобки — функция получает вthis этот объект. В данном случае user.sayHi() присвоит this = user.
Если одну и ту же функцию запускать в контексте разных объектов, она будет получать разный this:
Значение this не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.
Вызов в режиме обычной функции
Если функция использует this - это подразумевает работу с объектом. Но и прямой вызов func()технически возможен.
Как правило, такая ситуация возникает при ошибке в разработке.
При этом this получает значение window, глобального объекта.
В современном стандарте языка это поведение изменено, вместо глобального объекта this будетundefined.
…Но по умолчанию браузеры ведут себя по-старому.
Явное указание this: apply и call
Функцию можно вызвать, явно указав значение this.
Для этого у нее есть два метода: call и apply.
Метод call
Синтаксис метода call:
func.call(context, arg1, arg2,...) |
При этом вызывается функция func, первый аргумент call становится ее this, а остальные передаются «как есть».
Вызов func.call(context, a, b...) — то же, что обычный вызов func(a, b...), но с явно указанным контекстом context.
Например, функция showName в примере ниже вызывается через call в контексте объекта user:
Можно сделать ее более универсальной, добавив аргументы:
Здесь функция getName вызвана с контекстом this = user и выводит user['firstName'] иuser['surname'].
Метод apply
Метод call жестко фиксирует количество аргументов, через запятую:
f.call(context, 1, 2, 3); |
..А что, если мы захотим вызвать функцию с четырьмя аргументами? А что, если количество аргументов заранее неизвестно, и определяется во время выполнения?
Для решения этой задачи существует метод apply.
Вызов функции при помощи func.apply работает аналогично func.call, но принимает массив аргументов вместо списка:
func.call(context, arg1, arg2...) |
func.apply(context, [arg1, arg2 ... Об этом говорит сайт https://intellect.icu . ]); |
Эти две строчки cработают одинаково:
getName.call(user, 'firstName', 'surname'); |
getName.apply(user, ['firstName', 'surname']); |
Метод apply гораздо мощнее, чем call, так как можно сформировать массив аргументов динамически:
2 |
args.push('firstName'); |
5 |
func.apply(user, args); |
При указании первого аргумента null или undefined в call/apply, функция получаетthis = window:
Это поведение исправлено в современном стандарте (15.3).
Если функция работает в строгом режиме, то this передается «как есть»:
«Одалживание метода»
При помощи call/apply можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого.
В JavaScript методы объекта, даже встроенные — это функции. Поэтому можно скопировать функцию, даже встроенную, из одного объекта в другой.
Это называется «одалживание метода» (на англ. method borrowing).
Используем эту технику для упрощения манипуляций с arguments. Как мы знаем, это не массив, а обычный объект.. Но как бы хотелось вызывать на нем методы массива.
В строке (1) создали массив. У него есть метод [].join(..), но мы не вызываем его, а копируем, как и любое другое свойство в объект arguments. В строке (2) запустили его, как будто он всегда там был.
Здесь метод join массива скопирован и вызван в контексте arguments. Не произойдет ли что-то плохое от того, что arguments — не массив? Почему он, вообще, сработал?
Ответ на эти вопросы простой. В соответствии со спецификацией, внутри joinреализован примерно так:
01 |
function join(separator) { |
02 |
if (!this.length) return ''; |
06 |
for (var i = 1; i<this.length; i++) { |
07 |
str += separator + this[i]; |
Как видно, используется this, числовые индексы и свойство length. Если эти свойства есть, то все в порядке. А больше ничего и не нужно. Подходит даже обычный объект:
…Однако, прямое копирование метода не всегда приемлемо.
Представим на минуту, что вместо arguments у нас — произвольный объект, и мы хотим вызвать в его контексте метод [].join. Копировать этот метод, как мы делали выше, опасно: вдруг у объекта есть свой собственный join? Перезапишем, а потом что-то сломается..
Для безопасного вызова используем apply/call:
Мы вызвали метод без копирования. Чисто, безопасно.
Делаем из arguments настоящий Array
В JavaScript есть очень простой способ сделать из arguments настоящий массив. Для этого возьмем метод массива: arr.slice(start, end).
По стандарту он копирует часть массива arr от start до end в новый массив. А если start и end не указаны, то копирует весь массив.
Вызовем его в контексте arguments:
Как и в случае с join, такой вызов возможен потому, что slice использует от массива только нумерованные свойства и length. Все это в arguments есть.
«Переадресация» вызова через apply
При помощи apply мы можем сделать универсальную «переадресацию» вызова из одной функции в другую.
Например, функция f вызывает g в том же контексте, с теми же аргументами:
g.apply(this, arguments); |
Плюс этого подхода — в том, что он полностью универсален:
- Его не понадобится менять, если в
f добавятся новые аргументы.
- Если
f является методом объекта, то текущий контекст также будет передан. Если не является — то this здесь вроде как не при чем, но и вреда от него не будет.
Важность: 5
[Открыть задачу в новом окне]
Важность: 3
- Обычный вызов функции в контексте объекта.
- То же самое, скобки ни на что не влияют.
- Здесь не просто вызов
obj.method(), а более сложный вызов вида(выражение).method(). Такой вызов работает, как если бы он был разбит на две строки:
При этом f() выполняется как обычная функция, без передачи this.
- Здесь также слева от точки находится выражение, вызов аналогичен двум строкам.
В спецификации это объясняется при помощи специального внутреннего типаReference Type.
Если подробнее — то obj.go() состоит из двух операций:
- Сначала получить свойство
obj.go.
- Потом вызвать его как функцию.
Но откуда на шаге 2 получить this? Как раз для этого операция получения свойстваobj.go возвращает значение особого типа Reference Type, который в дополнение к свойству go содержит информацию об obj. Далее, на втором шаге, вызов его при помощи скобок () правильно устанавливает this.
Любые другие операции, кроме вызова, превращают Reference Type в обычный тип, в данном случае — функцию go (так уж этот тип устроен).
Поэтому получается, что (a = obj.go) присваивает в переменную a функцию go, уже без всякой информации об объекте obj.
Аналогичная ситуация и в случае (4): оператор ИЛИ || делает из Reference Typeобычную функцию.
[Открыть задачу в новом окне]
Важность: 2
Ошибка!
Попробуйте:
Причем сообщение об ошибке - очень странное. В большинстве браузеров этоobj is undefined.
Дело, как ни странно, ни в самом объявлении obj, а в том, что после него пропущена точка с запятой.
JavaScript игнорирует перевод строки перед скобкой (obj.go || ..) и читает этот код как:
var obj = { go:... }(obj.go || 0)() |
Интерпретатор попытается вычислить это выражение, которое обозначает вызов объекта { go: ... } как функции с аргументом (obj.go || 0). При этом, естественно, возникнет ошибка.
А что будет, если добавить точку с запятой?
2 |
go: function() { alert(this); } |
Все ли будет в порядке? Каков будет результат?
Результат — window, поскольку вызов obj.go || 0 аналогичен коду:
[Открыть задачу в новом окне]
Важность: 5
Вызов arr[2]() — это обращение к методу объекта obj[method](), в роли objвыступает arr, а в роли метода: 2.
Поэтому, как это бывает при вызове функции как метода, функция arr[2] получитthis = arr и выведет массив:
[Открыть задачу в новом окне]
Итого
Значение this устанавливается в зависимости от того, как вызвана функция:
- При вызове функции как метода
-
- При обычном вызове
-
- В
new -
- Явное указание
-
func.call(ctx, arg1, arg2, ...) |
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)