# Явное указание 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. В строке `(1)` объявлен пустой массив `[]` и скопирован его метод `[].join`. Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная -- обычное значение, мы можем скопировать любое свойство любого объекта, и `[].join` здесь не исключение.
  2. В строке `(2)` запустили `join` в контексте `arguments`, как будто он всегда там был.
  3. [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, ...) ```