За что отвечает первый аргумент в методе call javascript
Перейти к содержимому

За что отвечает первый аргумент в методе call javascript

  • автор:

Подробно о методах apply(), call() и bind(), необходимых каждому JavaScript разработчику

Stas Bagretsov

Вообще, эти методы довольно старые, но их понимание не становится от этого менее необходимым при изучении JavaScript. В этой статье вы узнаете о том, как работают apply() , call() и bind() , а также поймете на реальных примерах, что они на самом делают и как смогут вам пригодится в реальной жизни.

��Мой Твиттер — там много из мира фронтенда, да и вообще поговорим��. Подписывайтесь, будет интересно: ) ✈️

В JavaScript функции это объекты, как вы должны были бы уже знать. И как объекты, функции имеют свои методы, включая такие действенные, как apply() , call() и bind() . Можно сказать, что Apply() и Call() буквально идентичны друг другу и зачастую используются в JavaScript для того, чтобы заимствовать методы и выставлять значения this . Также мы используем Apply() для функций с большим количеством переменных и аргументов, но об этом вы узнаете дальше в статье.

Bind() же мы используем для выставления значения this в методах и для каррирования функций.

Мы рассмотрим каждый сценарий, в котором будет использоваться каждый из трех методов. Давайте начнем с метода bind() .

Метод bind()

В основном, мы используем метод bind() , чтобы вызывать функцию с указанием значения this . А другими словами, bind() позволяет нам легко выставлять какой именно объект будет привязан к this в момент вызова функции или метода.

Это может показаться относительно тривиальным, но зачастую значение this в методах и функциях должно быть выставлено явным способом, когда вам нужно привязать конкретный объект к значению this самой функции.

Обычно нам требуется bind() тогда, когда мы используем в методе this и вызываем сам метод из получающего объекта. В таких случаях, иногда this не привязывается к предполагаемому объекту, что само собой ведет к ошибке в работе кода. Так, сейчас без паники, если вы полностью не поняли предыдущее предложение. Всё скоро станет кристально ясным. За этим вы и читаете эту статью.

Перед тем, как взглянуть на код ниже, вы должны понять то, как работает this в JavaScript. Для этого прочтите статью — Подробно о том, как работает this в JavaScript. Если вы не будете понимать его работы, то вы встретите множество затруднений в понимании концепций, описанных ниже. Так что, лучше не ленитесь и прочтите её, потратьте время, оно вам окупится. А что будет ещё лучше, бонусом прочтите Разбираемся с “поднятием” (hoisting) в JavaScript

Bind() позволяет нам выставить значение this для метода

При клике на кнопку ниже, текстовое поле будет заполнено рандомным именем.

Итак, когда вы кликаете на кнопку, вы получаете ошибку, так как clickHandler() метод привязан к HTML элементу — кнопке, следовательно, он считается объектом на котором выполняется метод.

Вообще, эта проблема довольно распространена в JavaScript и фреймворки, такие как Backbone.js, вместе с библиотеками типа jQuery автоматически делают привязывание за нас, таким образом, что this всегда будет связан с объектом, работу с которым мы подразумеваем в нашем коде.

Чтобы исправить эту проблему в предыдущем примере, мы можем использовать метод bind() :

Следовательно, вместо этой строки:

Мы просто свяжем clickHandler с объектом user , как тут:

Давайте продолжим. Значение this , также привязывается к другому объекту, если мы назначаем метод (где this определено) на переменную. В этом примере вы это увидите:

Когда мы выполняем функцию showDataVar() , значения, выводимые в консоль идут из глобального data массива, а не из data массива внутри объекта user . Так получается, потому что showDataVar() выполняется как глобальная функция и использование this внутри showDataVar() привязано к глобальному полю видимости, что и является объектом window самого браузера.

И снова, мы можем пофиксить эту проблему, указав точно значения this c помощью метода bind() .

С помощью bind() мы можем заимствовать методы

В JavaScript мы можем передавать функции куда угодно, возвращать их, а ещё заимствовать и всё тому подобное. А с помощью метода bind() это будет просто ну супер как легко сделать и в особенности это касается заимствования методов.

Вот пример использования bind() , чтобы позаимствовать метод:

Тут проблема заключается в том, что мы добавляем новый метод ( showData ) объекту cars и возможно нам просто не нужно его заимствовать, потому что сам объект уже может иметь свойство или метод под названием showData . В общем, мы не хотим его случайно переписать. Как будет показано далее, на примерах Apply() и Call() , лучше всего заимствовать методы используя либо Apply() или Call() .

С помощью bind() мы можем каррировать функцию

Каррирование функции, также известное, как частичное применение функции — это использование функции, которая возвращает новую функцию с уже частично выставленными аргументами. Эта функция имеет доступ к хранящимся аргументам и переменным внешней функции. В общем, это звучит куда сложнее, чем есть на самом деле.

Давайте уже применим метод bind() и каррирование вместе. Для начала, у нас есть простенькая функция greet() , которая принимает 3 параметра:

И мы используем bind() метод, чтобы каррировать нашу функцию greet() . Как мы говорили ранее, первый аргумент метода bind() будет иметь значение this .

Используя метод bind() для каррировния, все наши параметры greet() , кроме последнего аргумента, подставляются автоматически. Таким образом этот аргумент, который мы меняем, используется при вызове новых функций, каррированных с greet() .

В общем, с помощью метода bind() , мы можем выставлять значение для вызова методов на объекте, заимствовать и копировать методы, а также назначать методы переменным, которые выполнятся как функции.

Методы Apply и Call

Два этих метода чуть ли не самые используемые в JavaScript и это неспроста: с их помощью мы можем заимствовать функции и выставлять значение this в вызове функции. Более того, функция apply() в буквальном смысле позволяет нам выполнять функцию в массиве параметров, как-будто каждый параметр передаётся функции индивидуально, при её выполнении — отличное решение для вариативных функций; такие функции берут варьирующееся число аргументов, а не заданное количество, как делается в большинстве функций.

Выставляем значение this с помощью Apply или Call

Как и в примере с bind() , мы также можем указывать значение для this, когда вызываем функцию используя методы apply() и call() . Первый параметр в call() и apply() выставляет this значение объекту на котором вызвана функция.

Вот очень быстрый и показательный пример для начинающих, перед тем, как мы окунёмся в использование Apply и Call:

А сейчас очень внимательно! Обратите внимание, что первый аргумент в call() выставляет значение this . В предыдущем примере, оно выставляется на объект gameController . А другие аргументы, после первого, передаются как параметры функции avg() .

Методы apply() и call() практически идентичны при работе с выставлением значения this , за исключением того, что вы передаёте параметры функции в apply() как массив, в то время, как в call() , параметры передаются в индивидуальном порядке. Но дальше ещё интереснее. У apply() есть ещё одна фича, которой нет у call() и очень скоро вы о ней узнаете.

Используем call() и apply(), чтобы выставлять this в Callback функциях

Сама функция, тут очень внимательно:

Метод apply() выставляет значение this для callbackObj . Так мы можем выполнить колбэк с указанным значением this , так что параметры переданные колбэку, будут выставлены на объекте clientData :

Методы apply() , call() и bind() всегда используются для того, чтобы выставлять значение this , при вызове метода, но каждый слегка по-своему и это отображается на том, как мы можем контролировать и поддерживать наш код. Значение this в JavaScript также важно, как и любая другая часть языка. Помните, что у нас есть 3 вышеупомянутых метода, которые просто необходимы для эффективной и правильной работы с этим самым this . Всегда старайтесь перепроверять то, что предполагается на месте this .

Заимствование функций с помощью Apply и Call (Важно знать)

Вообще Apply() и Call() в JavaScript используют в основном, чтобы заимствовать функции. С этими методами, мы можем выполнять заимствования также, как и с методом bind() , но при этом быть более гибкими. Давайте рассмотрим следующие примеры:

Заимствуем методы массива

У массивов есть несколько полезных методов для перебора и изменения массива, но к сожалению, у объектов нет стольких методов по-дефолту. Однако, мы можем выражать объект как массив (в виде массивоподобного или как иногда говорят — итерируемого объекта), так как все методы работы с массивами стандартны, мы можем смело их заимствовать и использовать на этих самых массивоподобных объектах.

Массивоподобный или итерируемый (array-like) объект — это объект ключи которого определены как неотрицательные целые числа. Они особенно хороши для добавления свойства length объекту. Почему? Потому что этого свойства не существует в объектах, но оно есть у массивов.

Тут нужно подчеркнуть, что в следующих примерах, когда мы вызываем Array.prototype , мы подтягиваем массив объекта и его прототип, в котором все его методы наследуются. И получается, что именно отсюда мы заимствуем методы массивов. Следовательно, использование кода, такого как Array.prototype.slice приведет к вызову метода slice, который определён в прототипе Array.

Давайте создадим массивоподобный объект и позаимствуем некоторые методы у массивов, чтобы применить их на нём же. Помните, что такой объект (array-like) это реальный объект и в целом это не массив:

Теперь, если вам надо применить какие-либо из распространенных методов массивов на нашем объекте, мы можем сделать следующее:

Тадам! Мы получаем все прелести работы над объектами и при этом нам доступны методы массивов, когда мы делаем наш объект массивоподобным и заимствуем дефолтные возможности работы с опять-таки массивами.

Объект arguments который является свойством всех функций в JavaScript — является массивоподобным объектом и по этой причине, одним из самых популярных применений call() и apply() методов это извлечение параметров переданных функции из этого объекта.

Переменная args это реальный массив. В нём содержатся все параметры, переданные функции transitionTo .

С этого примера мы узнали быстрый способ получения всех аргументов, переданных функции. А именно:

Мы обсудим то, как использовать метод apply() c arguments массивоподобным объектом немного позже, но уже на примере вариативных функций.

Заимствование строчных методов с помощью Apply() и Call()

Как и в предыдущем примере, мы также можем использовать apply() и call() , чтобы заимствовать строчные методы. Так как строки неизменяемые (иммутабельны), то только неманипулятивные массивы работают с ними, так что вы не сможете использовать reverse, pop и т.п.

Заимствование чужих методов и функций

Так как мы заимствуем, то давайте пойдем ва-банк и позаимствуем наши собственные кастомные методы и функции, а не только из Array (массивов) или из String (строк):

Будьте уверены, это не сколько просто, сколько рекомендуется — заимствовать наши собственные кастомные функции и методы. Объект gameController заимствует метод appController под именем avg() . Значение this, определенное в методе avg() будет выставлено первым параметром — то есть объектом gameController .

Вы возможно хотите знать, что случится если метод, который мы заимствуем изменится. Изменится ли сам заимствованный (скопированный) метод или этот метод останется полной копией оригинала без любой взаимосвязи с ним? Давайте ответим на эти вопросы быстрым и показательным примером:

Как и ожидалось, если мы изменим оригинальный метод, то изменения коснуться заимствованных примеров этого метода. Это ожидаемо по причине того, что мы никогда не делаем полную копию метода, мы просто её заимствуем (Ссылаясь на её актуальную реализацию).

Используем apply() для выполнения функций с составными переменными

Чтобы окончательно закончить наш разговор на тему многогранности и полезности методов Apply() , Call() и Bind() , мы обсудим одну очень интересную возможность, которую нам даёт метод Apply() . А именно, выполнение функции с массивом аргументов.

Мы можем передать массив с аргументами функции и воспользовавшись преимуществом метода apply() , функция будет работать с элементами массива так, как если бы мы запустили её таким образом:

Эта техника, в частности, используется для создания функций из составных переменных или как ещё известно — вариативных функций.

Это функции, которые принимают любое число аргументов, а не фиксированное. Арность функции указывает число аргументов, которое функции было определено принять.

Math.max() метод это общеизвестный пример вариативной функции в JavaScript:

Но, что если у нас массив из чисел, которые нам нужно передать к Math.max ? Вот так мы сделать не сможем:

Вот тут, с вариативными функциями, нам и поможет метод apply(). Вместо того, что мы увидели сверху, мы передадим массив чисел используя apply(), следовательно, код будет таким:

Как мы уже раньше узнали, первый аргумент в apply() выставляет значение this , но this не используется в Math.max() методе, поэтому мы передаём null .

Вот пример вариативной фукнции, чтобы ещё лучше проиллюстрировать концепцию использования метода apply() :

Заключение

Call() , Apply() и Bind() методы определенно рабочие лошадки и просто обязаны быть частью вашего JavaScript инструментария, особенно в плане выставления значения this у функций, для создания и выполнения вариативных функций и конечно же для заимствования методов и функций. Как JavaScript разработчик, вы с большой долей вероятности будете полагаться и использовать эти функции, снова и снова. Так что убедитесь в том, что вы хорошо их поняли.

Function.prototype.call()

The call() method calls the function with a given this value and arguments provided individually.

Try it

Syntax

Parameters

The value to use as this when calling func . If the function is not in strict mode, null and undefined will be replaced with the global object, and primitive values will be converted to objects.

arg1, …, argN Optional

Arguments for the function.

Return value

The result of calling the function with the specified this value and arguments.

Description

Note: This function is almost identical to apply() , except that the function arguments are passed to call() individually as a list, while for apply() they are combined in one object, typically an array — for example, func.call(this, «eat», «bananas») vs. func.apply(this, [«eat», «bananas»]) .

Normally, when calling a function, the value of this inside the function is the object that the function was accessed on. With call() , you can assign an arbitrary value as this when calling an existing function, without first attaching the function to the object as a property. This allows you to use methods of one object as generic utility functions.

Warning: Do not use call() to chain constructors (for example, to implement inheritance). This invokes the constructor function as a plain function, which means new.target is undefined , and classes throw an error because they can’t be called without new . Use Reflect.construct() or extends instead.

Examples

Using call() to invoke a function and specifying the this value

In the example below, when we call greet , the value of this will be bound to object obj , even when greet is not a method of obj .

Using call() to invoke a function without specifying the first argument

If the first thisArg parameter is omitted, it defaults to undefined . In non-strict mode, the this value is then substituted with globalThis (which is akin to the global object).

In strict mode, the value of this is not substituted, so it stays as undefined .

Явное указание this: "call", "apply"

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/call-apply-decorators.

Итак, мы знаем, что this – это текущий объект при вызове «через точку» и новый объект при конструировании через new .

В этой главе наша цель получить окончательное и полное понимание this в JavaScript. Для этого не хватает всего одного элемента: способа явно указать this при помощи методов call и apply .

Метод call

Синтаксис метода call :

При этом вызывается функция func , первый аргумент call становится её this , а остальные передаются «как есть».

Вызов func.call(context, a, b. ) – то же, что обычный вызов func(a, b. ) , но с явно указанным this(=context) .

Например, у нас есть функция showFullName , которая работает с this :

Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать this везде. Любая функция может в своём коде упомянуть this , каким будет это значение – выяснится в момент запуска.

Вызов showFullName.call(user) запустит функцию, установив this = user , вот так:

После контекста в call можно передать аргументы для функции. Вот пример с более сложным вариантом showFullName , который конструирует ответ из указанных свойств объекта:

«Одалживание метода»

При помощи call можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого.

Это называется «одалживание метода» (на англ. method borrowing).

Используем эту технику для упрощения манипуляций с arguments .

Как мы знаем, arguments не массив, а обычный объект, поэтому таких полезных методов как push , pop , join и других у него нет. Но иногда так хочется, чтобы были…

Нет ничего проще! Давайте скопируем метод join из обычного массива:

  1. В строке (1) объявлен пустой массив [] и скопирован его метод [].join . Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная – обычное значение, мы можем скопировать любое свойство любого объекта, и [].join здесь не исключение.
  2. В строке (2) запустили join в контексте arguments , как будто он всегда там был.

Здесь метод join массива скопирован и вызван в контексте arguments . Не произойдёт ли что-то плохое от того, что arguments – не массив? Почему он, вообще, сработал?

Ответ на эти вопросы простой. В соответствии со спецификацией, внутри join реализован примерно так:

Как видно, используется this , числовые индексы и свойство length . Если эти свойства есть, то все в порядке. А больше ничего и не нужно.

В качестве this подойдёт даже обычный объект:

…Однако, копирование метода из одного объекта в другой не всегда приемлемо!

Представим на минуту, что вместо arguments у нас – произвольный объект. У него тоже есть числовые индексы, length и мы хотим вызвать в его контексте метод [].join . То есть, ситуация похожа на arguments , но (!) вполне возможно, что у объекта есть свой метод join .

Поэтому копировать [].join , как сделано выше, нельзя: если он перезапишет собственный join объекта, то будет страшный бардак и путаница.

Безопасно вызвать метод нам поможет call :

Мы вызвали метод без копирования. Чисто, безопасно.

Ещё пример: [].slice.call(arguments)

В JavaScript есть очень простой способ сделать из arguments настоящий массив. Для этого возьмём метод массива: slice.

По стандарту вызов arr.slice(start, end) создаёт новый массив и копирует в него элементы массива arr от start до end . А если start и end не указаны, то копирует весь массив.

Вызовем его в контексте arguments :

Как и в случае с join , такой вызов технически возможен потому, что slice для работы требует только нумерованные свойства и length . Всё это в arguments есть.

Метод apply

Если нам неизвестно, с каким количеством аргументов понадобится вызвать функцию, можно использовать более мощный метод: apply .

Вызов функции при помощи func.apply работает аналогично func.call , но принимает массив аргументов вместо списка.

В частности, эти две строчки сработают одинаково:

Преимущество apply перед call отчётливо видно, когда мы формируем массив аргументов динамически.

Например, в JavaScript есть встроенная функция Math.max(a, b, c. ) , которая возвращает максимальное значение из аргументов:

При помощи apply мы могли бы найти максимум в произвольном массиве, вот так:

В примере выше мы передали аргументы через массив – второй параметр apply … Но вы, наверное, заметили небольшую странность? В качестве контекста this был передан null .

Строго говоря, полным эквивалентом вызову Math.max(1,2,3) был бы вызов Math.max.apply(Math, [1,2,3]) . В обоих этих вызовах контекстом будет объект Math .

Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод Math.max не использует this . Действительно, зачем this , если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи apply мы получили короткий и элегантный способ вычислить максимальное значение в массиве!

В современном стандарте call/apply передают this «как есть». А в старом, без use strict , при указании первого аргумента null или undefined в call/apply , функция получает this = window , например:

Функции

— это блок программного кода на языке JavaScript, который определяется один раз и может выполняться, или вызываться, многократно. Возможно, вы уже знакомы с понятием «функция» под другим названием, таким как подпрограмма, или процедура. Функции могут иметь параметры: определение функции может включать список идентификаторов, которые называются параметрами и играют роль локальных переменных в теле функции.

При вызове функций им могут передаваться значения, или аргументы, соответствующие их параметрам. Функции часто используют свои аргументы для вычисления возвращаемого значения, которое является значением выражения вызова функции. В дополнение к аргументам при вызове любой функции ей передается еще одно значение, определяющее контекст вызова — значение в ключевом слове this.

Функции в языке JavaScript являются объектами и могут использоваться разными способами. Например, функции могут присваиваться переменным и передаваться другим функциям. Поскольку функции являются объектами, имеется возможность присваивать значения их свойствам и даже вызывать их методы.

В JavaScript допускается создавать определения функций, вложенные в другие функции, и такие функции будут иметь доступ ко всем переменным, присутствующим в области видимости определения.

Определение функций

Определение функции начинается с ключевого слова function, за которым указываются следующие компоненты:

Идентификатор, определяющий имя функции

Имя является обязательной частью инструкции объявления функции: оно будет использовано для создания новой переменной, которой будет присвоен объект новой функции. В выражениях определения функций имя может отсутствовать: при его наличии имя будет ссылаться на объект функции только в теле самой функции.

Пара круглых скобок вокруг списка из нуля или более идентификаторов, разделенных запятыми

Эти идентификаторы будут определять имена параметров функции и в теле функции могут использоваться как локальные переменные.

Пара фигурных скобок с нулем или более инструкций JavaScript внутри

Эти инструкции составляют тело функции: они выполняются при каждом вызове функции.

В следующем примере показано несколько определений функций в виде инструкций и выражений. Обратите внимание, что определения функций в виде выражений удобно использовать, только если они являются частью более крупных выражений, таких как присваивание или вызов функции, которые выполняют некоторые действия с помощью вновь объявленной функции:

Обратите внимание, что в выражениях определения функций имя функции может отсутствовать. Инструкция объявления функции фактически объявляет переменную и присваивает ей объект функции.

Выражение определения функции, напротив, не объявляет переменную. Однако в выражениях определения допускается указывать имя функции, как в функции вычисления факториала выше, которое может потребоваться в теле функции для вызова себя самой. Если выражение определения функции включает имя, данное имя будет ссылаться на объект функции в области видимости этой функции. Фактически имя функции становится локальной переменной, доступной только в теле функции. В большинстве случаев имя функции не требуется указывать в выражениях определения, что делает определения более компактными.

Обратите внимание, что большинство (но не все) функций в примере содержат инструкцию return. Инструкция return завершает выполнение функции и выполняет возврат значения своего выражения (если указано) вызывающей программе. Если выражение в инструкции return отсутствует, она возвращает значение undefined. Если инструкция return отсутствует в функции, интерпретатор просто выполнит все инструкции в теле функции и вернет вызывающей программе значение undefined.

Большинство функций в примере вычисляют некоторое значение, и в них инструкция return используется для возврата этого значения вызывающей программе. Функция printprops() несколько отличается в этом смысле: ее работа заключается в том, чтобы вывести имена свойств объекта. Ей не нужно возвращать какое-либо значение, поэтому в функции отсутствует инструкция return. Функция printprops() всегда будет возвращать значение undefined. (Функции, не имеющие возвращаемого значения, иногда называются процедурами.)

Вызов функций

Программный код, образующий тело функции, выполняется не в момент определения функции, а в момент ее вызова. Вызов функций выполняется с помощью выражения вызова. Выражение вызова состоит из выражения обращения к функции, которое возвращает объект функции, и следующими за ним круглыми скобками со списком из нуля или более выражений-аргументов, разделенных запятыми, внутри.

Если выражение обращения к функции является выражением обращения к свойству — если функция является свойством объекта или элементом массива (т.е. методом) — тогда выражение вызова является выражением вызова метода. В следующем фрагменте демонстрируется несколько примеров выражений вызова обычных функций:

При вызове функции вычисляются все выражения-аргументы (указанные между скобками), и полученные значения используются в качестве аргументов функции. Эти значения присваиваются параметрам, имена которых перечислены в определении функции. В теле функции выражения обращений к параметрам возвращают значения соответствующих аргументов.

При вызове обычной функции возвращаемое функцией значение становится значением выражения вызова. Если возврат из функции происходит по достижении ее конца интерпретатором, возвращается значение undefined. Если возврат из функции происходит в результате выполнения инструкции return, возвращается значение выражения, следующего за инструкцией return, или undefined, если инструкция return не имеет выражения.

Метод — это не что иное, как функция, которая хранится в виде свойства объекта. Если имеется функция func и объект obj, то можно определить метод объекта obj с именем method, как показано ниже:

Чаще всего при вызове методов используется форма обращения к свойствам с помощью оператора точки, однако точно так же можно использовать форму обращения к свойствам с помощью квадратных скобок. Например, оба следующих выражения являются выражениями вызова методов:

Аргументы и возвращаемое значение при вызове метода обрабатываются точно так же, как при вызове обычной функции. Однако вызов метода имеет одно важное отличие: контекст вызова. Выражение обращения к свойству состоит из двух частей: объекта (в данном случае obj) и имени свойства (method). В подобных выражениях вызова методов объект obj становится контекстом вызова, и тело функции получает возможность ссылаться на этот объект с помощью ключевого слова this. Например:

Методы и ключевое слово this занимают центральное место в парадигме объектно-ориентированного программирования. Любая функция, используемая как метод, фактически получает неявный аргумент — объект, относительно которого она была вызвана. Как правило, методы выполняют некоторые действия с объектом, и синтаксис вызова метода наглядно отражает тот факт, что функция оперирует объектом.

Обратите внимание: this — это именно ключевое слово, а не имя переменной или свойства. Синтаксис JavaScript не допускает возможность присваивания значений элементу this.

Аргументы и параметры функций

В языке JavaScript, в определениях функций не указываются типы параметров, а при вызове функций не выполняется никаких проверок типов передаваемых значений аргументов. Фактически при вызове функций в языке JavaScript не проверяется даже количество аргументов. В подразделах ниже описывается, что происходит, если число аргументов в вызове функции меньше или больше числа объявленных параметров. В них также демонстрируется, как можно явно проверить типы аргументов функции, если необходимо гарантировать, что функция не будет вызвана с некорректными аргументами.

Необязательные аргументы

Когда число аргументов в вызове функции меньше числа объявленных параметров, недостающие аргументы получают значение undefined. Часто бывает удобным писать функции так, чтобы некоторые аргументы были необязательными и могли опускаться при вызове функции. В этом случае желательно предусмотреть возможность присваивания достаточно разумных значений по умолчанию параметрам, которые могут быть опущены. Например:

Обратите внимание, что при объявлении функций необязательные аргументы должны завершать список аргументов, чтобы их можно было опустить. Программист, который будет писать обращение к вашей функции, не сможет передать второй аргумент и при этом опустить первый: он будет вынужден явно передать в первом аргументе значение undefined. Обратите также внимание на комментарий /* необязательный */ в определении функции, который подчеркивает тот факт, что параметр является необязательным.

Списки аргументов переменной длины

Если число аргументов в вызове функции превышает число имен параметров, функция лишается возможности напрямую обращаться к неименованным значениям. Решение этой проблемы предоставляет объект Arguments. В теле функции идентификатор arguments ссылается на объект Arguments, присутствующий в вызове. Объект Arguments — это объект, подобный массиву, позволяющий извлекать переданные функции значения по их номерам, а не по именам.

Предположим, что была определена функция func, которая требует один аргумент x. Если вызвать эту функцию с двумя аргументами, то первый будет доступен внутри функции по имени параметра x или как arguments[0]. Второй аргумент будет доступен только как arguments[1]. Кроме того, подобно настоящим массивам, arguments имеет свойство length, определяющее количество содержащихся элементов. То есть в теле функции func, вызываемой с двумя аргументами, arguments.length имеет значение 2.

Объект Arguments может использоваться с самыми разными целями. Следующий пример показывает, как с его помощью проверить, была ли функция вызвана с правильным числом аргументов, — ведь JavaScript этого за вас не сделает:

Обратите внимание, что зачастую нет необходимости проверять количество аргументов, как в данном примере. Поведение по умолчанию интерпретатора JavaScript отлично подходит для большинства случаев: отсутствующие аргументы замещаются значением undefined, а лишние аргументы просто игнорируются.

Объект Arguments иллюстрирует важную возможность JavaScript-функций: они могут быть написаны таким образом, чтобы работать с любым количеством аргументов. Следующая функция принимает любое число аргументов и возвращает значение самого большого из них (аналогично ведет себя встроенная функция Math.max()):

Функции, подобные этой и способные принимать произвольное число аргументов, называются . Этот термин возник вместе с появлением языка программирования C.

Обратите внимание, что функции с переменным числом аргументов не должны допускать возможность вызова с пустым списком аргументов. Будет вполне разумным использовать объект arguments[] при написании функции, ожидающей получить фиксированное число обязательных именованных аргументов, за которыми может следовать произвольное число необязательных неименованных аргументов.

Не следует забывать, что arguments фактически не является массивом — это объект Arguments. В каждом объекте Arguments имеются пронумерованные элементы массива и свойство length, но с технической точки зрения это не массив. Лучше рассматривать его как объект, имеющий некоторые пронумерованные свойства.

Помимо элементов своего массива объект Arguments определяет свойства callee и caller. При попытке изменить значения этих свойств в строгом режиме ECMAScript 5 гарантированно возбуждается исключение TypeError. Однако в нестрогом режиме стандарт ECMAScript утверждает, что свойство callee ссылается на выполняемую в данный момент функцию. Свойство caller не является стандартным, но оно присутствует во многих реализациях и ссылается на функцию, вызвавшую текущую.

Свойство caller можно использовать для доступа к стеку вызовов, а свойство callee особенно удобно использовать для рекурсивного вызова неименованных функций:

Свойства и методы функций

Мы видели, что в JavaScript-программах функции могут использоваться как значения. Оператор typeof возвращает для функций строку «function», однако в действительности функции в языке JavaScript — это особого рода объекты. А раз функции являются объектами, то они имеют свойства и методы, как любые другие объекты. Существует даже конструктор Function(), который создает новые объекты функций. В следующих подразделах описываются свойства и методы функций.

Свойство length

В теле функции свойство arguments.length определяет количество аргументов, переданных функции. Однако свойство length самой функции имеет иной смысл. Это свойство, доступное только для чтения, возвращает количество аргументов, которое функция ожидает получить — число объявленных параметров.

В следующем фрагменте определяется функция с именем check(), получающая массив аргументов arguments от другой функции. Она сравнивает свойство arguments.length (число фактически переданных аргументов) со свойством arguments.callee.length (число ожидаемых аргументов), чтобы определить, передано ли функции столько аргументов, сколько она ожидает. Если значения не совпадают, генерируется исключение. За функцией check() следует тестовая функция func(), демонстрирующая порядок использования функции check():

Свойство prototype

Любая функция имеет свойство prototype, ссылающееся на объект, известный как объект прототипа. Каждая функция имеет свой объект прототипа. Когда функция используется в роли конструктора, вновь созданный объект наследует свойства этого объекта прототипа.

Прототипы и свойство prototype обсуждались в предыдущей статье.

Методы call() и apply()

Методы call() и apply() позволяют выполнять косвенный вызов функции, как если бы она была методом некоторого другого объекта. Первым аргументом обоим методам, call() и apply(), передается объект, относительно которого вызывается функция; этот аргумент определяет контекст вызова и становится значением ключевого слова this в теле функции. Чтобы вызвать функцию func() (без аргументов) как метод объекта obj, можно использовать любым из методов, call() или apply():

Любой из этих способов вызова эквивалентен следующему фрагменту (где предполагается, что объект obj не имеет свойства с именем m):

В строгом режиме ECMAScript 5 первый аргумент методов call() и apply() становится значением this, даже если это простое значение, null или undefined. В ECMAScript 3 и в нестрогом режиме значения null и undefined замещаются глобальным объектом, а простое значение — соответствующим объектом-оберткой.

Все остальные аргументы метода call(), следующие за первым аргументом, определяющим контекст вызова, передаются вызываемой функции. Метод apply() действует подобно методу call(), за исключением того, что аргументы для функции передаются в виде массива. Если функция способна обрабатывать произвольное число аргументов, метод apply() может использоваться для вызова такой функции в контексте массива произвольной длины.

В следующем примере демонстрируется практическое применение метода call():

Метод bind()

Метод bind() впервые появился в ECMAScript 5, но его легко имитировать в ECMAScript 3. Как следует из его имени, основное назначение метода bind() состоит в том, чтобы связать (bind) функцию с объектом. Если вызвать метод bind() функции func и передать ему объект obj, он вернет новую функцию. Вызов новой функции (как обычной функции) выполнит вызов оригинальной функции func как метода объекта obj. Любые аргументы, переданные новой функции, будут переданы оригинальной функции. Например:

Такой способ связывания легко реализовать в ECMAScript 3, как показано ниже:

Метод bind() в ECMAScript 5 не просто связывает функцию с объектом. Он также выполняет частичное применение: помимо значения this связаны будут все аргументы, переданные методу bind() после первого его аргумента. Частичное применение — распространенный прием в функциональном программировании и иногда называется каррингом (currying).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *