Метод call и его особенности
Метод вызывает функцию с новым значением this и переданными аргументами. Приведу немного кода и постараюсь объяснить, что происходит:
let slice = Array.prorotype.slice; slice.call([1,2,3])
Мы скопировали ссылку на метод slice в переменную. Далее вызываем метод. Однако, простой вызов slice([1,2,3]) отработает некорректно, поскольку метод ожидает контекст извне. В этой связи мы, используя call , передаем нужный методу контекст и все работает. Но я не очень хочу каждый раз передавать контекст. Поэтому делаю следующее:
slice = Function.prototype.call.bind(Array.prototype.slice);
Теперь можно вызывать метод без call :
slice([1,2,3]);
Все работает замечательно. Но проблема в том, что я не понимаю, как. Ведь метод slice принимает итерируемый объект в качестве this , судя по вызову slice.call([1,2,3]) или [1,2,3].slice() . Когда мы добавляем контекст методу call через bind таким способом slice = Function.prototype.call.bind(Array.prototype.slice) мы, по идее, возвращаем обертку в которой this = Array.prototype.slice . Ну и при следующем вызове slice([1,2,3]) я ожидаю, что выполняется такой код Function.prototype.call(Array.prototype.slice, [1,2,3]) . Тогда, каким образом это работает? В документации написано, что Function.prototype.call принимает первым аргументом контекст, а следующими аргументы функции. Но как он знает о самой функции? Понятно, когда мы на какой-то функции вызывает call , типа slice.call . А когда мы делаем это через прототип? У меня одна догадка, что, когда мы вызываем функцию call через прототип Function.prototype.call , то первым аргументом передаем саму функцию, вторым контекст и дальше аргументы. Но я нигде не могу нагуглить информацию, которая подтверждала бы мои догадки. Подскажите, пожалуйста, как на самом деле работает этот вызов:
slice = Function.prototype.call.bind(Array.prototype.slice);
Метод call()
С методом call() вы можете вызывать любые методы для использования с разными объектами.
Все функции — методы
В JavaScript все функции являются методами объектов.
Если какая-то функция не является методом какого-либо объекта JavaScript, то это функция глобального объекта.
В следующем примере создается объект с 3 свойствами – firstName, lastName, fullName:
var person = < firstName:"John", lastName: "Doe", fullName: function () < return this.firstName + " " + this.lastName; >> person.fullName(); // вернет "John Doe"
Ключевое слово this
В определении функции ключевое слово this ссылается на «владельца» этой функции.
В предыдущем примере this – это объект person, который «владеет» функцией fullName.
Другими словами выражение this.firstName означает свойство firstName этого объекта.
JavaScript метод call()
Метод call() это предопределенный метод JavaScript. Он может использоваться для вызова любого метода с любым владельцем объекта в качестве аргумента (параметра).
Благодаря методу call() любой объект может использовать методы, принадлежащие другому объекту.
В следующем примере вызывается метод fullName, принадлежащий объекту person, для использования с объектом person1:
var person = < fullName: function() < return this.firstName + " " + this.lastName; >> var person1 = < firstName:"John", lastName: "Doe", >var person2 = < firstName:"Mary", lastName: "Doe", >person.fullName.call(person1); // вернет "John Doe"
В следующем примере вызывается метод fullName, принадлежащий объекту person, для использования с объектом person2:
var person = < fullName: function() < return this.firstName + " " + this.lastName; >> var person1 = < firstName:"John", lastName: "Doe", >var person2 = < firstName:"Mary", lastName: "Doe", >person.fullName.call(person2); // вернет "Mary Doe"
Метод call() с аргументам
Метод call() может принимать аргументы:
var person = < fullName: function(city, country) < return this.firstName + " " + this.lastName + "," + city + "," + country; >> var person1 = < firstName:"John", lastName: "Doe", >person.fullName.call(person1, "Oslo", "Norway");
Явное указание this: «call», «apply»
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/call-apply-decorators.
Итак, мы знаем, что this – это текущий объект при вызове «через точку» и новый объект при конструировании через new .
В этой главе наша цель получить окончательное и полное понимание this в JavaScript. Для этого не хватает всего одного элемента: способа явно указать this при помощи методов call и apply .
Метод call
Синтаксис метода call :
func.call(context, arg1, arg2, . )
При этом вызывается функция func , первый аргумент call становится её this , а остальные передаются «как есть».
Вызов func.call(context, a, b. ) – то же, что обычный вызов func(a, b. ) , но с явно указанным this(=context) .
Например, у нас есть функция showFullName , которая работает с this :
function showFullName()
Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать this везде. Любая функция может в своём коде упомянуть this , каким будет это значение – выяснится в момент запуска.
Вызов showFullName.call(user) запустит функцию, установив this = user , вот так:
function showFullName() < alert( this.firstName + " " + this.lastName ); >var user = < firstName: "Василий", lastName: "Петров" >; // функция вызовется с this=user showFullName.call(user) // "Василий Петров"
После контекста в call можно передать аргументы для функции. Вот пример с более сложным вариантом showFullName , который конструирует ответ из указанных свойств объекта:
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 из обычного массива:
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 , как будто он всегда там был.
Почему вызов сработает?
Здесь метод join массива скопирован и вызван в контексте arguments . Не произойдёт ли что-то плохое от того, что arguments – не массив? Почему он, вообще, сработал?
Ответ на эти вопросы простой. В соответствии со спецификацией, внутри join реализован примерно так:
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 подойдёт даже обычный объект:
var obj = < // обычный объект с числовыми индексами и length 0: "А", 1: "Б", 2: "В", length: 3 >; obj.join = [].join; alert( obj.join(';') ); // "A;Б;В"
…Однако, копирование метода из одного объекта в другой не всегда приемлемо!
Представим на минуту, что вместо arguments у нас – произвольный объект. У него тоже есть числовые индексы, length и мы хотим вызвать в его контексте метод [].join . То есть, ситуация похожа на arguments , но (!) вполне возможно, что у объекта есть свой метод join .
Поэтому копировать [].join , как сделано выше, нельзя: если он перезапишет собственный join объекта, то будет страшный бардак и путаница.
Безопасно вызвать метод нам поможет call :
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 :
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 , но принимает массив аргументов вместо списка.
func.call(context, arg1, arg2); // идентичен вызову func.apply(context, [arg1, arg2]);
В частности, эти две строчки сработают одинаково:
showFullName.call(user, 'firstName', 'surname'); showFullName.apply(user, ['firstName', 'surname']);
Преимущество apply перед call отчётливо видно, когда мы формируем массив аргументов динамически.
Например, в JavaScript есть встроенная функция Math.max(a, b, c. ) , которая возвращает максимальное значение из аргументов:
alert( Math.max(1, 5, 2) ); // 5
При помощи apply мы могли бы найти максимум в произвольном массиве, вот так:
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 мы получили короткий и элегантный способ вычислить максимальное значение в массиве!
Вызов call/apply с null или undefined
В современном стандарте call/apply передают this «как есть». А в старом, без use strict , при указании первого аргумента null или undefined в call/apply , функция получает this = window , например:
Function.prototype.call()
Метод call() вызывает функцию с указанным значением this и индивидуально предоставленными аргументами.
Примечание: хотя синтаксис этой функции практически полностью идентичен функции apply() , фундаментальное различие между ними заключается в том, что функция call() принимает список аргументов, в то время, как функция apply() — одиночный массив аргументов.
Синтаксис
fun.call(thisArg[, arg1[, arg2[, . ]]])
Параметры
Значение this , предоставляемое для вызова функции fun . Обратите внимание, что this может не быть реальным значением, видимым этим методом: если метод является функцией в нестрогом режиме (en-US), значения null и undefined будут заменены глобальным объектом, а примитивные значения будут упакованы в объекты.
Аргументы для объекта.
Описание
Вы можете присваивать различные объекты this при вызове существующей функции. this ссылается на текущий объект, вызвавший объект. С помощью call вы можете написать метод один раз, а затем наследовать его в других объектах, без необходимости переписывать метод для каждого нового объекта.
Примеры
Пример: использование call для связи конструкторов объекта в цепочку
Вы можете использовать метод call для объединения в цепочку конструкторов объекта, как в Java. В следующем примере для объекта продукта Product объявлен конструктор с двумя параметрами, названием name и ценой price . Продукт инициализирует свойства name и price , а специализированные функции определяют ещё категорию category .
function Product(name, price) this.name = name; this.price = price; if (price 0) throw RangeError( "Нельзя создать продукт " + this.name + " с отрицательной ценой", ); > > function Food(name, price) Product.call(this, name, price); this.category = "еда"; > Food.prototype = Object.create(Product.prototype); function Toy(name, price) Product.call(this, name, price); this.category = "игрушка"; > Toy.prototype = Object.create(Product.prototype); var cheese = new Food("фета", 5); var fun = new Toy("робот", 40);
Пример: использование call для вызова анонимной функции
В этом чисто искусственном примере, мы создаём анонимную функцию и используем call для вызова её на каждом элементе массива. Главная задача анонимной функции здесь — добавить функцию печати в каждый объект, способную напечатать правильный индекс объекта в массиве. Передача объекта в качестве значения this не является острой необходимостью, но мы делаем это в целях объяснения.
var animals = [ species: "Лев", name: "Король" >, species: "Кит", name: "Фэйл" >, ]; for (var i = 0; i animals.length; i++) (function (i) this.print = function () console.log("#" + i + " " + this.species + ": " + this.name); >; this.print(); >).call(animals[i], i); >
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-function.prototype.call |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Function.prototype.bind()
- Function.prototype.apply()
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
