# Явное указание this: "call", "apply"
Итак, мы знаем, что `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`.
В этой главе наша цель получить окончательное и полное понимание `this` в JavaScript. Для этого не хватает всего одного элемента: способа явно указать `this` при помощи методов `call` и `apply`.
[cut]
## Метод call
Синтаксис метода `call`:
```js
func.call(context, arg1, arg2, ...)
```
При этом вызывается функция `func`, первый аргумент `call` становится её `this`, а остальные передаются "как есть".
**Вызов `func.call(context, a, b...)` -- то же, что обычный вызов `func(a, b...)`, но с явно указанным `this(=context)`.**
Например, у нас есть функция `showFullName`, которая работает с `this`:
```js
function showFullName() {
alert( this.firstName + " " + this.lastName );
}
```
Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска.
Вызов `showFullName.call(user)` запустит функцию, установив `this = user`, вот так:
```js
//+ run
function showFullName() {
alert( this.firstName + " " + this.lastName );
}
var user = {
firstName: "Василий",
lastName: "Петров"
};
*!*
// функция вызовется с this=user
showFullName.call(user) // "Василий Петров"
*/!*
```
После контекста в `call` можно передать аргументы для функции. Вот пример с более сложным вариантом `showFullName`, который конструирует ответ из указанных свойств объекта:
```js
//+ run
var user = {
firstName: "Василий",
surname: "Петров",
patronym: "Иванович"
};
function showFullName(firstPart, lastPart) {
alert( this[firstPart] + " " + this[lastPart] );
}
*!*
// f.call(контекст, аргумент1, аргумент2, ...)
showFullName.call(user, 'firstName', 'surname') // "Василий Петров"
showFullName.call(user, 'firstName', 'patronym') // "Василий Иванович"
*/!*
```
## "Одалживание метода"
При помощи `call` можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого.
Это называется "одалживание метода" (на англ. *method borrowing*).
**Используем эту технику для упрощения манипуляций с `arguments`.**
Как мы знаем, `arguments` не массив, а обычный объект, поэтому таких полезных методов как `push`, `pop`, `join` и других у него нет. Но иногда так хочется, чтобы были...
Нет ничего проще! Давайте скопируем метод `join` из обычного массива:
```js
//+ run
function printArgs() {
arguments.join = [].join; // одолжили метод (1)
var argStr = arguments.join(':'); // (2)
alert( argStr ); // сработает и выведет 1:2:3
}
printArgs(1, 2, 3);
```
- В строке `(1)` объявлен пустой массив `[]` и скопирован его метод `[].join`. Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная -- обычное значение, мы можем скопировать любое свойство любого объекта, и `[].join` здесь не исключение.
- В строке `(2)` запустили `join` в контексте `arguments`, как будто он всегда там был.
[smart header="Почему вызов сработает?"]
Здесь метод join массива скопирован и вызван в контексте `arguments`. Не произойдёт ли что-то плохое от того, что `arguments` -- не массив? Почему он, вообще, сработал?
Ответ на эти вопросы простой. В соответствии [со спецификацией](http://es5.github.com/x15.4.html#x15.4.4.5), внутри `join` реализован примерно так:
```js
function join(separator) {
if (!this.length) return '';
var str = this[0];
for (var i = 1; i < this.length; i++) {
str += separator + this[i];
}
return str;
}
```
Как видно, используется `this`, числовые индексы и свойство `length`. Если эти свойства есть, то все в порядке. А больше ничего и не нужно.
В качестве `this` подойдёт даже обычный объект:
```js
//+ run
var obj = { // обычный объект с числовыми индексами и length
0: "А",
1: "Б",
2: "В",
length: 3
};
*!*
obj.join = [].join;
alert( obj.join(';') ); // "A;Б;В"
*/!*
```
[/smart]
...Однако, копирование метода из одного объекта в другой не всегда приемлемо!
Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`.
Поэтому копировать `[].join`, как сделано выше, нельзя: если он перезапишет собственный `join` объекта, то будет страшный бардак и путаница.
Безопасно вызвать метод нам поможет `call`:
```js
//+ run
function printArgs() {
var join = [].join; // скопируем ссылку на функцию в переменную
*!*
// вызовем join с this=arguments,
// этот вызов эквивалентен arguments.join(':') из примера выше
var argStr = join.call(arguments, ':');
*/!*
alert( argStr ); // сработает и выведет 1:2:3
}
printArgs(1, 2, 3);
```
Мы вызвали метод без копирования. Чисто, безопасно.
## Ещё пример: [].slice.call(arguments)
В JavaScript есть очень простой способ сделать из `arguments` настоящий массив. Для этого возьмём метод массива: slice.
По стандарту вызов `arr.slice(start, end)` создаёт новый массив и копирует в него элементы массива `arr` от `start` до `end`. А если `start` и `end` не указаны, то копирует весь массив.
Вызовем его в контексте `arguments`:
```js
//+ run
function printArgs() {
// вызов arr.slice() скопирует все элементы из this в новый массив
*!*
var args = [].slice.call(arguments);
*/!*
alert( args.join(', ') ); // args - полноценный массив из аргументов
}
printArgs('Привет', 'мой', 'мир'); // Привет, мой, мир
```
Как и в случае с `join`, такой вызов технически возможен потому, что `slice` для работы требует только нумерованные свойства и `length`. Всё это в `arguments` есть.
## Метод apply
Если нам неизвестно, с каким количеством аргументов понадобится вызвать функцию, можно использовать более мощный метод: `apply`.
**Вызов функции при помощи `func.apply` работает аналогично `func.call`, но принимает массив аргументов вместо списка.**
```js
func.call(context, arg1, arg2);
// идентичен вызову
func.apply(context, [arg1, arg2]);
```
В частности, эти две строчки cработают одинаково:
```js
showFullName.call(user, 'firstName', 'surname');
showFullName.apply(user, ['firstName', 'surname']);
```
Преимущество `apply` перед `call` отчётливо видно, когда мы формируем массив аргументов динамически.
Например, в JavaScript есть встроенная функция `Math.max(a, b, c...)`, которая возвращает максимальное значение из аргументов:
```js
//+ run
alert( Math.max(1, 5, 2) ); // 5
```
При помощи `apply` мы могли бы найти максимум в произвольном массиве, вот так:
```js
//+ run
var arr = [];
arr.push(1);
arr.push(5);
arr.push(2);
// получить максимум из элементов arr
alert( Math.max.apply(null, arr) ); // 5
```
В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`.
Строго говоря, полным эквивалентом вызову `Math.max(1,2,3)` был бы вызов `Math.max.apply(Math, [1,2,3])`. В обоих этих вызовах контекстом будет объект `Math`.
Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max` не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве!
[smart header="Вызов `call/apply` с `null` или `undefined`"]
В современном стандарте `call/apply` передают `this` "как есть". А в старом, без `use strict`, при указании первого аргумента `null` или `undefined` в `call/apply`, функция получает `this = window`, например:
Современный стандарт:
```js
//+ run
function f() {
"use strict";
*!*
alert( this ); // null
*/!*
}
f.call(null);
```
Без `use strict`:
```js
//+ run
function f() {
alert( this ); // window
}
f.call(null);
```
[/smart]
## Итого про this
Значение `this` устанавливается в зависимости от того, как вызвана функция:
- При вызове функции как метода
-
```js
//+ no-beautify
obj.func(...) // this = obj
obj["func"](...)
```
- При обычном вызове
-
```js
func(...) // this = window (ES3) /undefined (ES5)
```
- В `new`
-
```js
new func() // this = {} (новый объект)
```
- Явное указание
-
```js
func.apply(context, args) // this = context (явная передача)
func.call(context, arg1, arg2, ...)
```