Function.prototype.apply()

Общие сведения

Метод apply() вызывает функцию с указанным значением this и аргументами, предоставленными в виде массива (либо массивоподобного объекта (en-US)).

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

Синтаксис

fun.apply(thisArg, [argsArray])

Параметры

thisArg

Значение this, предоставляемое для вызова функции fun. Обратите внимание, что this может не быть реальным значением, видимым этим методом: если метод является функцией в нестрогом режиме (en-US), значения null и undefined будут заменены глобальным объектом, а примитивные значения будут упакованы в объекты.

argsArray Необязательный

Массивоподобный объект, определяющий аргументы, с которыми функция fun должна быть вызвана, либо null или undefined, если в функцию не надо передавать аргументы. Начиная с ECMAScript 5 эти аргументы могут быть обобщёнными массивоподобными объектами, а не только массивом. Смотрите ниже информацию по совместимости с браузерами.

Описание

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

Метод apply очень похож на метод call(), за исключением поддерживаемого типа аргументов. Вы можете использовать массив аргументов вместо набора именованных параметров. Вместе с apply вы можете использовать литерал массива, например, fun.apply(this, ['есть', 'бананы']), либо объект Array, например, fun.apply(this, new Array('есть', 'бананы')).

Также вы можете использовать в качестве параметра argsArray псевдомассив arguments (en-US). arguments является локальной переменной функции. Он может использоваться для всех неопределённых аргументов вызываемого объекта. Таким образом, вы не обязаны знать, сколько и какие аргументы требует вызываемый объект при использовании метода apply(). Вы можете использовать псевдомассив arguments для передачи всех аргументов в вызываемый объект. Вызываемый объект самостоятельно разберётся с обработкой аргументов.

Начиная с 5-го издания ECMAScript, вы также можете использовать любой вид массивоподобного объекта, что на практике означает, что он должен иметь свойство length и целочисленные свойства в диапазоне (0...length). В качестве примера, теперь вы можете использовать NodeList или свой собственный объект вида { 'length': 2, '0': 'есть', '1': 'бананы' }.

Примечание: Большинство браузеров, включая Chrome 14 и Internet Explorer 9, всё ещё не принимают массивоподобные объекты и будут выбрасывать исключение.

Примеры

Пример: использование apply() для связи конструкторов объекта в цепочку

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

js
Function.prototype.construct = function (aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};

Примечание: метод Object.create(), использованный в этом примере, относительно новый. В качестве альтернативного способа можно рассмотреть возможность использования замыкания:

js
Function.prototype.construct = function (aArgs) {
  var fConstructor = this,
    fNewConstr = function () {
      fConstructor.apply(this, aArgs);
    };
  fNewConstr.prototype = fConstructor.prototype;
  return new fNewConstr();
};

Пример использования:

js
function MyConstructor() {
  for (var nProp = 0; nProp < arguments.length; nProp++) {
    this["property" + nProp] = arguments[nProp];
  }
}

var myArray = [4, "Привет, мир!", false];
var myInstance = MyConstructor.construct(myArray);

alert(myInstance.property1); // выведет 'Привет, мир!'
alert(myInstance instanceof MyConstructor); // выведет 'true'
alert(myInstance.constructor); // выведет 'MyConstructor'

Примечание: этот неродной метод Function.construct() не будет работать с некоторыми родными конструкторами (вроде конструктора Date, к примеру). В этих случаях вы можете использовать метод Function.prototype.bind() (например, представьте, что вы имеете следующий массив, который можно использовать с конструктором Date: [2012, 11, 4]; в этом случае вы напишите что-то вроде: new (Function.prototype.bind.apply(Date, [null].concat([2012, 11, 4])))() — так или иначе, это не самый изящный способ и, вероятно, его не стоит использовать в рабочем окружении).

Пример: использование apply() и встроенных функций

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

js
/* мин/макс числа в массиве */
var numbers = [5, 6, 2, 3, 7];

/* используем apply к Math.min/Math.max */
var max = Math.max.apply(
  null,
  numbers,
); /* Это эквивалентно Math.max(numbers[0], ...)
                                            или Math.max(5, 6, ...) */
var min = Math.min.apply(null, numbers);

/* сравним с простым алгоритмом с циклом */
(max = -Infinity), (min = +Infinity);

for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] > max) {
    max = numbers[i];
  }
  if (numbers[i] < min) {
    min = numbers[i];
  }
}

Но будьте осторожны: при использовании метода apply() таким образом вы рискуете выйти за пределы ограничения на количество аргументов в движке JavaScript. Последствия применения функции с очень большим количеством аргументов (думается, больше десяти тысяч аргументов) различаются от движка к движку (JavaScriptCore имеет жёстко зашитое ограничение на количество аргументов в 65536), поскольку этот предел (на самом деле, это природа поведения любого чрезвычайно огромного стека) не определён. Некоторые движки будут выкидывать исключение. Хуже того, другие просто отбрасывают реально переданные функции аргументы сверх лимита. (Для иллюстрации последнего случая: если такой движок имеет ограничение в четыре элемента [реальное ограничение, конечно же, гораздо выше], это выглядело бы так, как если бы в примере выше в метод apply() были переданы аргументы 5, 6, 2, 3, а не весь массив.) Если ваш массив значений может вырасти до десятков тысяч, используйте смешанный подход: применяйте вашу функцию к порциям массива:

js
function minOfArray(arr) {
  var min = Infinity;
  var QUANTUM = 32768;

  for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
    var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
    min = Math.min(submin, min);
  }

  return min;
}

var min = minOfArray([5, 6, 2, 3, 7]);

Пример: использование apply() в «monkey-патчинге»

Метод apply() может быть лучшим вариантом для «monkey-патчинга» встроенных в Firefox функций, либо JS библиотек. Пусть у вас есть функция someobject.foo(), вы можете изменить её таким немного хакерским способом:

js
var originalfoo = someobject.foo;
someobject.foo = function () {
  // Делаем что-то до вызова функции
  console.log(arguments);
  // Вызываем функцию так, как будто бы она была вызвана обычным образом:
  originalfoo.apply(this, arguments);
  // Делаем что-то после вызова функции.
};

Этот метод особенно удобен, когда вам нужно отладить события, либо интерфейс с чем-то, что не имеет API, вроде различных событий .on([event]..., например, тех что используются в Инспекторе инструментов разработчика).

Спецификации

Specification
ECMAScript Language Specification
# sec-function.prototype.apply

Совместимость с браузерами

BCD tables only load in the browser

Смотрите также