Значение 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)