Несколько аргументов против объекта параметров

157

При создании функции JavaScript с несколькими аргументами я всегда сталкиваюсь с этим выбором: передать список аргументов или передать объект параметров.

Например, я пишу функцию для отображения nodeList в массив:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

Я мог бы вместо этого использовать это:

function map(options){
    ...
}

где options это объект:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

Какой из них рекомендуется? Существуют ли руководящие указания относительно того, когда использовать один против другого?

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

Christophe
источник
2
Второй вариант дает вам именованные аргументы, что, на мой взгляд, приятно.
Вернер Квалем Вестерос 10.10.12
Это необязательные или обязательные аргументы?
Я ненавижу Ленивых
@ user1689607 в моем примере последние три не являются обязательными.
Кристоф
Поскольку ваши последние два аргумента очень похожи, если пользователь передаст только один или другой, вы никогда не сможете узнать, какой из них был задуман. Из-за этого вам почти потребуются именованные аргументы. Но я могу оценить, что вы хотите поддерживать API, похожий на нативный API.
Я ненавижу Ленивых
1
Моделирование по нативному API не плохо, если ваша функция делает что-то похожее. Все сводится к тому, «что делает код наиболее читабельным». Array.prototype.mapимеет простой API, который не должен вызывать недоумение у любого опытного программиста.
Джереми Дж. Старчер

Ответы:

160

Как и многие другие, я часто предпочитаю передавать функцию options objectв функцию, а не передавать длинный список параметров, но это действительно зависит от точного контекста.

Я использую читаемость кода в качестве лакмусовой бумажки.

Например, если у меня есть вызов этой функции:

checkStringLength(inputStr, 10);

Я думаю, что код вполне читабелен, и передача отдельных параметров - это нормально.

С другой стороны, есть функции с такими вызовами:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

Абсолютно нечитабельно, если вы не сделаете некоторые исследования. С другой стороны, этот код хорошо читается:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

Это больше искусство, чем наука, но если бы мне пришлось назвать практические правила:

Используйте параметр options, если:

  • У вас есть более четырех параметров
  • Любой из параметров является необязательным
  • Вам когда-нибудь приходилось искать функцию, чтобы выяснить, какие параметры она принимает
  • Если кто-то когда-нибудь попытается тебя задушить, крича "ARRRRRG!"
Джереми Дж. Старчер
источник
10
Отличный ответ. Это зависит. Остерегайтесь булевых ловушек ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Тревор Диксон
2
Ах, да ... Я забыл об этой ссылке. Это действительно заставило меня переосмыслить работу API, и я даже переписал несколько фрагментов кода, узнав, что делал глупости. Спасибо!
Джереми Дж. Старчер
1
«Вам когда-либо приходилось искать функцию, чтобы выяснить, какие параметры она принимает». Вместо этого, используя объект параметров, вам не всегда придется искать метод, чтобы выяснить, нужны ли ключи и как они называются. Intellisense в среде IDE не отображает эту информацию, а параметры -. В большинстве IDE вы можете просто навести указатель мыши на метод, и он покажет вам, что это за параметры.
simbolo
1
Если вас всех интересуют последствия производительности для этого «наилучшего метода кодирования», вот тестирование jsPerf в обоих направлениях: jsperf.com/function-boolean-arguments-vs-options-object/10 Обратите внимание на небольшой поворот в Третий вариант, где я использую объект «предустановленных» (постоянных) опций, что может быть сделано, когда у вас много вызовов (в течение жизненного цикла, например, веб-страницы) с одинаковыми настройками, известными во время разработки (вкратце : когда значения вашего параметра довольно жестко заданы в вашем исходном коде).
Гер Хоббелт
2
@Sean Честно говоря, я больше не использую этот стиль кодирования вообще. Я переключился на TypeScript и использовал именованные параметры.
Джереми Дж. Старчер
28

Несколько аргументов в основном для обязательных параметров. С ними все в порядке.

Если у вас есть дополнительные параметры, это становится сложным. Если один из них зависит от других, так что они имеют определенный порядок (например, четвертому нужен третий), вам все равно следует использовать несколько аргументов. Почти все нативные EcmaScript и DOM-методы работают следующим образом. Хорошим примером является openметод XMLHTTP-запросов , где последние 3 аргумента являются необязательными - правило похоже на «нет пароля без пользователя» (см. Также документы MDN ).

Опциональные объекты пригодятся в двух случаях:

  • У вас так много параметров, что это сбивает с толку: «Наименование» поможет вам, вам не нужно беспокоиться о порядке их (особенно, если они могут измениться)
  • У вас есть дополнительные параметры. Объекты очень гибкие, и без какого-либо упорядочивания вы просто передаете то, что вам нужно, и ничего больше (или undefined).

В твоем случае я бы порекомендовал map(nodeList, callback, options). nodelistи callbackявляются обязательными, остальные три аргумента приходят только изредка и имеют разумные значения по умолчанию.

Еще один пример JSON.stringify. Возможно, вы захотите использовать spaceпараметр без передачи replacerфункции - тогда вам придется вызывать …, null, 4). Объект аргументов мог бы быть лучше, хотя на самом деле это не очень разумно только для двух параметров.

Берги
источник
+1 тот же вопрос, что и у @ trevor-dixon: видели ли вы этот микс на практике, например, в библиотеках js?
Кристоф
Примером могут служить ajax-методы jQuery . Они принимают [обязательный] URL в качестве первого аргумента и огромный аргумент параметров в качестве второго аргумента.
Берги
так странно! Я никогда не замечал этого раньше. Я всегда видел, как это используется с URL-адресом в качестве свойства опции ...
Кристоф
Да, jQuery делает странные вещи со своими необязательными параметрами, оставаясь обратно совместимым :-)
Bergi
1
На мой взгляд, это единственный вменяемый ответ здесь.
Бенджамин Грюнбаум
11

Лучше всего использовать подход «параметры как объект». Вам не нужно беспокоиться о порядке свойств, и есть большая гибкость в том, какие данные передаются (например, необязательные параметры)

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

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}
Fluidbyte
источник
Здесь (разумное) предположение состоит в том, что объект всегда будет иметь одинаковые пары ключей. Если вы работаете с дерьмовым / несовместимым API, то у вас другая проблема.
Backdesk
9

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

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

В вашем примере я бы сделал map(nodeList, callback, options). Требуются нодлист и обратный вызов, довольно просто узнать, что происходит, просто прочитав вызов, и это похоже на существующие функции карты. Любые другие параметры могут быть переданы в качестве необязательного третьего параметра.

Тревор Диксон
источник
+1 интересно. Вы видели это на практике, например, в библиотеках JS?
Кристоф
7

Ваш комментарий к вопросу:

в моем примере последние три не являются обязательными.

Так почему бы не сделать это? (Примечание: это довольно сырой Javascript. Обычно я использую defaultхеш и обновляю его с помощью параметров, передаваемых с помощью Object.extend или JQuery.extend или аналогичных ...)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

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

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});
Izkata
источник
Это похоже на ответ @ trevor-dixon.
Кристоф
5

Возможно, я немного опоздал на вечеринку с этим ответом, но я искал мнения других разработчиков по этой самой теме и наткнулся на эту тему.

Я очень не согласен с большинством респондентов и придерживаюсь подхода «множественных аргументов». Моим главным аргументом является то, что он препятствует другим анти-шаблонам, таким как «мутирование и возврат объекта param» или «передача того же объекта param другим функциям». Я работал над базами кода, которые широко использовали этот анти-шаблон, и отладка кода, который делает это быстро, становится невозможной. Я думаю, что это очень эмпирическое правило для Javascript, поскольку Javascript не является строго типизированным и допускает такие произвольно структурированные объекты.

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

Рассмотрим следующий ужасный код:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

Этот тип требует не только более явной документации для определения намерения, но также оставляет место для неопределенных ошибок. Что делать, если разработчик изменяет param1вbar() ? Как вы думаете, сколько времени потребуется, чтобы просмотреть кодовую базу достаточного размера, чтобы это уловить? Следует признать, что этот пример немного неискренен, поскольку предполагает, что к этому моменту разработчики уже применили несколько анти-паттернов. Но это показывает, как передача объектов, содержащих параметры, дает больше места для ошибок и двусмысленности, требуя большей степени добросовестности и соблюдения правильности const.

Просто мои два цента по этому вопросу!

ajxs
источник
3

Это зависит.

Основываясь на моих наблюдениях за дизайном этих популярных библиотек, вот сценарии, которые мы должны использовать option object:

  • Список параметров длинный (> 4).
  • Некоторые или все параметры являются необязательными и не зависят от определенного порядка.
  • Список параметров может увеличиться в будущем обновлении API.
  • API будет вызываться из другого кода, а имя API недостаточно ясно, чтобы понять значение параметров. Поэтому для удобства чтения может потребоваться строгое имя параметра.

И сценарии использования списка параметров:

  • Список параметров короткий (<= 4).
  • Большинство или все параметры обязательны.
  • Необязательные параметры находятся в определенном порядке. (т.е.: $ .get)
  • Легко сказать параметры значения по имени API.
Линн Нин
источник
2

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

happyCoda
источник
1

Для функции, которая обычно использует некоторые предопределенные аргументы, лучше использовать опцию object. Противоположным примером будет что-то вроде функции, которая получает бесконечное количество аргументов, таких как: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).

Илья Сидорович
источник
0

Я бы посмотрел на большие проекты Javascript.

Такие вещи, как карта Google, вы часто увидите, что для экземпляров объектов требуются объекты, а функциям - параметры. Я думаю, что это связано с OPTION argumemnts.

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

У Javascript тоже есть argumentsобъект. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

dm03514
источник