JavaScript переменное количество аргументов для функции

531

Есть ли способ разрешить "неограниченные" переменные для функции в JavaScript?

Пример:

load(var1, var2, var3, var4, var5, etc...)
load(var1)
Timo
источник
связанный / возможный дубликат stackoverflow.com/questions/4633125/…
Лука
1
@ Люк нет, это не так. Этот вопрос спрашивает, как вызвать функцию с произвольным числом аргументов с аргументами в массиве. Это спрашивает, как обработать такой вызов.
Скраффи
1
Для облегчения поиска такая функция называется «переменной функцией».
gschenk

Ответы:

814

Конечно, просто используйте argumentsобъект.

function foo() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}
roufamatic
источник
1
Tnx. Он отлично подходит для анализа строк из собственного кода Android в javascript в Webview.
Йохан Хоксма
4
Это решение работало лучше всего для меня. Спасибо. Дополнительная информация по аргументам ключевого слова ЗДЕСЬ .
User2
26
argumentsэто специальный объект типа «массив», что означает, что он имеет длину, но не имеет других функций массива. См. Developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… для получения дополнительной информации, и этот ответ: stackoverflow.com/a/13145228/1766230
Лука
4
Интересно, что методы Array в Javascript были определены таким образом, что они работают с любым объектом, у которого есть lengthсвойство. Это включает в себя argumentsобъект. Зная это, и что метод concatвозвращает копию из «массива» это называется, мы можем легко преобразовать argumentsобъект в реальный массив , как это: var args = [].concat.call(arguments). Некоторые люди предпочитают использовать Array.prototype.concat.callвместо этого, но мне нравится [], они короткие и сладкие!
Стейн де Витт
2
@YasirJan использовать[...arguments].join()
Виллиан Д. Андраде
179

В (большинстве) последних браузеров вы можете принимать переменное количество аргументов с этим синтаксисом:

function my_log(...args) {
     // args is an Array
     console.log(args);
     // You can pass this array as parameters to another function
     console.log(...args);
}

Вот небольшой пример:

function foo(x, ...args) {
  console.log(x, args, ...args, arguments);
}

foo('a', 'b', 'c', z='d')

=>

a
Array(3) [ "b", "c", "d" ]
b c d
Arguments
    0: "a"
    1: "b"
    2: "c"
    3: "d"
    length: 4

Документация и другие примеры здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters

Ramast
источник
27
К вашему сведению это называется «синтаксис остальных параметров»: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
tanguy_k
4
+1 Это элегантное и чистое решение. Особенно подходит для передачи длинного списка параметров в вызов другой функции, и, возможно, эти переменные параметры находятся в произвольной позиции.
haxpor
3
Имейте в виду, что в соответствии с developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… это не поддерживается в IE.
Роуи Гавирель
2
Вот таблица, показывающая поддержку браузера - caniuse.com/#feat=rest-parameters
Брайан Бернс
1
Такой хороший ответ!
Ашиш
113

Другой вариант - передать аргументы в объекте контекста.

function load(context)
{
    // do whatever with context.name, context.address, etc
}

и использовать это так

load({name:'Ken',address:'secret',unused:true})

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

кругозор
источник
4
Это было бы лучше, так как убирает связь с порядком аргументов. Слабосвязанные интерфейсы - хорошая стандартная практика ...
Йонас Шуберт Эрландссон
9
Конечно, это лучше в некоторых случаях. Но допустим, что отдельные аргументы на самом деле не связаны друг с другом или все должны иметь одинаковое значение (например, элементы массива). Тогда OP лучше всего.
rvighne
1
Это также хорошо, потому что, если вы хотите, вы можете создать contextаргумент с кодом и передать его, прежде чем он будет использован.
Nate CK
46

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

function load(context) {

    var defaults = {
        parameter1: defaultValue1,
        parameter2: defaultValue2,
        ...
    };

    var context = extend(defaults, context);

    // do stuff
}

Таким образом, если у вас много параметров, но вам не обязательно устанавливать их при каждом вызове функции, вы можете просто указать значения по умолчанию. Для метода extends вы можете использовать метод extend ( $.extend()) jQuery , создать свой собственный или использовать следующее:

function extend() {
    for (var i = 1; i < arguments.length; i++)
        for (var key in arguments[i])
            if (arguments[i].hasOwnProperty(key))
                arguments[0][key] = arguments[i][key];
    return arguments[0];
}

Это объединит объект контекста со значениями по умолчанию и заполнит все неопределенные значения в вашем объекте значениями по умолчанию.

mbeasley
источник
3
+1. Хороший трюк. Сохраняет много котельной плиты, чтобы каждый параметр был определен, по умолчанию или иначе.
Нил
1
_.defaults()Метод Underscore является очень хорошей альтернативой объединению заданных аргументов и аргументов по умолчанию.
mbeasley
18

Да, вот так:

function load()
{
  var var0 = arguments[0];
  var var1 = arguments[1];
}

load(1,2);
Canavar
источник
18

Предпочтительно использовать синтаксис параметра rest, как указал Ramast.

function (a, b, ...args) {}

Я просто хочу добавить хорошее свойство аргумента ... args

  1. Это массив, а не объект, как аргументы. Это позволяет вам применять такие функции, как карта или сортировка напрямую.
  2. Он включает не все параметры, а только тот, который был передан от него. Например, функция (a, b, ... args) в этом случае args содержит аргумент 3 для arguments.length
Никола Педретти
источник
15

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

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

log() {
    let args = Array.prototype.slice.call(arguments);
    args = ['MyObjectName', this.id_].concat(args);
    console.log.apply(console, args);
}
Питер Ценг
источник
Хорошее решение для преобразования аргументов в массив. Это было полезно для меня сегодня.
Дмитрий Медведь
8

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

function load(context) {
   var parameter1 = context.parameter1 || defaultValue1,
       parameter2 = context.parameter2 || defaultValue2;

   // do stuff
}

Это примерно такой же объем кода (может быть, немного больше), но он должен составлять часть затрат времени выполнения.

mcurland
источник
Договорились, хотя вред зависит от типа стоимости или самого дефолта. В противном случае (parameter1=context.parameter1)===undefined && (parameter1=defaultValue1)или для меньшего объема кода небольшая вспомогательная функция, такая как: function def(providedValue, default) {return providedValue !== undefined ? providedValue : defaultValue;} var parameter1 = def(context.parameter1, defaultValue1)предоставляет альтернативные шаблоны. Однако моя точка зрения остается неизменной: создание дополнительных объектов для каждого вызова функции и запуск дорогостоящих циклов для установки пары значений по умолчанию - это сумасшедшие затраты.
mcurland
7

Хотя @roufamatic продемонстрировал использование ключевого слова arguments и @Ken показал отличный пример объекта для использования, я не чувствую истинного понимания того, что происходит в этом случае, и может запутать будущих читателей или привить плохую практику, поскольку явно не указывается функция / Метод предназначен для получения переменного количества аргументов / параметров.

function varyArg () {
    return arguments[0] + arguments[1];
}

Когда другой разработчик просматривает ваш код, очень легко предположить, что эта функция не принимает параметры. Особенно, если этот разработчик не знаком с ключевым словом arguments . Из-за этого хорошей идеей будет следовать руководству по стилю и быть последовательным. Я буду использовать Google для всех примеров.

Давайте прямо заявим, что та же функция имеет переменные параметры:

function varyArg (var_args) {
    return arguments[0] + arguments[1];
}

Параметр объекта VS var_args

Могут быть случаи, когда объект необходим, так как это единственный одобренный и рассмотренный передовой метод карты данных. Ассоциативные массивы не одобряются и не одобряются.

SIDENOTE: ключевое слово arguments на самом деле возвращает объект, используя числа в качестве ключа. Прототип наследования также является объектным семейством. Смотрите конец ответа для правильного использования массива в JS

В этом случае мы можем это явно указать. Примечание: это соглашение об именах не предоставляется Google, но является примером явного объявления типа параметра. Это важно, если вы хотите создать более строгий типизированный шаблон в вашем коде.

function varyArg (args_obj) {
    return args_obj.name+" "+args_obj.weight;
}
varyArg({name: "Brian", weight: 150});

Какой выбрать?

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

Использование аргументов

function sumOfAll (var_args) {
    return arguments.reduce(function(a, b) {
        return a + b;
    }, 0);
}
sumOfAll(1,2,3); // returns 6

Использование объекта

function myObjArgs(args_obj) {
    // MAKE SURE ARGUMENT IS AN OBJECT OR ELSE RETURN
    if (typeof args_obj !== "object") {
        return "Arguments passed must be in object form!";
    }

    return "Hello "+args_obj.name+" I see you're "+args_obj.age+" years old.";
}
myObjArgs({name: "Brian", age: 31}); // returns 'Hello Brian I see you're 31 years old

Доступ к массиву вместо объекта ("... args" параметр rest)

Как уже упоминалось в начале ответа, ключевое слово arguments на самом деле возвращает объект. Поэтому любой метод, который вы хотите использовать для массива, должен быть вызван. Пример этого:

Array.prototype.map.call(arguments, function (val, idx, arr) {});

Чтобы избежать этого, используйте параметр rest:

function varyArgArr (...var_args) {
    return var_args.sort();
}
varyArgArr(5,1,3); // returns 1, 3, 5
Брайан Эллис
источник
5

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

nickytonline
источник
3

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

phbcanada
источник