Об'єкт декількох аргументів проти опцій


157

Створюючи функцію JavaScript з декількома аргументами, я завжди стикаюся з цим вибором: передайте список аргументів проти передачі об'єкта параметрів.

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

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

Я можу замість цього використати:

function map(options){
    ...
}

де параметри є об’єктом:

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

Який із них рекомендований спосіб? Чи є вказівки щодо використання одного проти іншого?

[Оновлення] Здається, що існує консенсус на користь об’єкта параметрів, тому я хотів би додати коментар: одна з причин, чому я спокусився використовувати список аргументів у моєму випадку, - це поведінка, що відповідає JavaScript вбудований метод array.map.


2
Другий варіант дає вам названі аргументи, що є приємною справою на мою думку.
Werner Kvalem Vesterås

Вони є необов'язковими чи необхідними аргументами?
Я ненавиджу ледачий

@ user1689607 в моєму прикладі останні три необов’язкові.
Крістоф

Оскільки два останні ваші аргументи дуже схожі, якби користувач передав лише один чи інший, ви ніколи не зможете дізнатись, який із них призначений. Через це вам майже потрібні названі аргументи. Але я можу оцінити, що ви хочете підтримувати API, подібний до рідного API.
Я ненавиджу ледачий

1
Моделювання після нативної API не є поганою справою, якщо ваша функція робить щось подібне. Все зводиться до того, "що робить код найбільш читаючим". Array.prototype.mapмає простий API, який не повинен залишати жодного напівдосвідченого кодера спантеличеним.
Jeremy J Starcher

Відповіді:


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
 });

Це скоріше мистецтво, ніж наука, але якби мені довелося назвати великі правила:

Використовуйте параметр параметрів, якщо:

  • У вас більше чотирьох параметрів
  • Будь-який із параметрів необов’язковий
  • Вам коли-небудь доводилося шукати функцію, щоб визначити, які параметри вона потрібна
  • Якщо хтось колись намагається задушити вас, кричачи "ARRRRRG!"

10
Чудова відповідь. Це залежить. Остерігайтеся булевих пасток ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Тревор Діксон

2
О так ... я забув про це посилання. Це насправді змусило мене переосмислити, як працюють API, і я навіть переписав кілька фрагментів коду після того, як дізнався, що я робив дурні речі. Дякую!
Jeremy J Starcher

1
"Вам коли-небудь доводилося шукати функцію, щоб визначити, які параметри вона потрібна" - За допомогою об'єкта параметрів замість цього вам не доведеться завжди шукати метод, щоб зрозуміти, що потрібні ключі та як вони викликаються. Intellisense в IDE не передає цю інформацію, в той час як парами це роблять. У більшості IDE ви можете просто навести курсор миші на метод, і він покаже, що таке парами.
simbolo

1
Якщо вас цікавлять ефективність цього біта "кодування найкращих практик", ось jsPerf тестує обидва способи: jsperf.com/function-boolean-arguments-vs-options-object/10 Зверніть увагу на невеликий поворот у третя альтернатива, де я використовую об'єкт "попередньо встановлених" (постійних) параметрів, який можна робити, коли у вас є багато дзвінків (протягом життя вашого часу виконання роботи, наприклад, ваша веб-сторінка) з тими ж налаштуваннями, відомими під час розробки (коротше) : коли ваші значення параметрів начебто жорстко кодуються у вихідному коді).
Ger Hobbelt

2
@Sean Чесно кажучи, я більше не використовую цей стиль кодування. Я перейшов на TypeScript і використовував названі параметри.
Джеремі Дж. Старчер

28

Кілька аргументів здебільшого стосуються обов’язкових параметрів. У них нічого поганого.

Якщо у вас є необов’язкові параметри, це ускладнюється. Якщо одна з них покладається на інші, щоб вони мали певний порядок (наприклад, четвертому потрібен третій), ви все одно повинні використовувати кілька аргументів. Практично всі подібні методи EcmaScript і DOM працюють так. Хорошим прикладом є openметод XMLHTTPrequests , де останні три аргументи необов’язкові - правило схоже на "без пароля без користувача" (див. Також документи 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);
}

Тут (розумне) припущення полягає в тому, що об'єкт завжди матиме однакові пари ключів. Якщо ви працюєте з придурливим / непослідовним API, то у вас виникає інша проблема.
бекдек

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,
});

Це схоже на відповідь @ trevor-dixon.
Крістоф

5

Я, можливо, трохи спізнився з учасником цієї відповіді, але я шукав думки інших розробників на цю саму тему і натрапив на цю тему.

Я дуже не погоджуюсь з більшістю респондентів і сторони з підходом до "багатьох аргументів". Мій головний аргумент полягає в тому, що він відштовхує від інших анти-шаблонів, таких як "мутація та повернення парам-об'єкта" або "передача того ж об'єкта параму на інші функції". Я працював у кодових базах, які широко зловживали цим анти-шаблоном, і налагоджувальний код, який робить це, швидко стає неможливим. Я думаю, що це дуже специфічне для 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()? Як довго, на вашу думку, знадобиться перегляд кодової бази достатнього розміру, щоб зрозуміти це? Справді, цей приклад є дещо непомітним, оскільки він передбачає, що розробники вже скористалися декількома анти-шаблонами до цього моменту. Але це показує, як передача об'єктів, що містять параметри, дає більше можливостей для помилок та неоднозначності, вимагаючи більшої ступеня сумлінності та дотримання коректності коректності.

Просто мої два центи з питання!


3

Це залежить.

На основі мого спостереження над дизайном популярних бібліотек, ось такі сценарії, якими ми повинні використовувати параметр об'єкта:

  • Список параметрів довгий (> 4).
  • Деякі або всі параметри необов’язкові, і вони не залежать від певного порядку.
  • Список параметрів може зрости в майбутньому оновлення API.
  • API буде викликано з іншого коду, а ім'я API недостатньо зрозуміле, щоб визначити значення параметрів. Таким чином, для читабельності може знадобитися сильна назва параметра.

І сценарії використання списку параметрів:

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

2

Об'єкт є більш кращим, тому що якщо ви передаєте об'єкт, його легко розширити кількість властивостей у цих об'єктах, і вам не доведеться стежити за порядком передачі ваших аргументів.


1

Для функції, яка зазвичай використовує деякі заздалегідь задані аргументи, краще використовувати об’єкт опції. Протилежним прикладом буде щось на зразок функції, яка отримує нескінченну кількість аргументів на зразок: setCSS ({висота: 100}, {ширина: 200}, {фон: "# 000"}).


0

Я б дивився на великі проекти JavaScript.

Такі речі, як карта Google, ви часто бачите, що для об'єктів, що створюються, потрібний об'єкт, але для функцій потрібні параметри. Я думаю, що це стосується аргументів OPTION.

Якщо вам потрібні аргументи за замовчуванням або необов’язкові аргументи, об’єкт, ймовірно, буде кращим, оскільки він є більш гнучким. Але якщо ви не нормальні, функціональні аргументи є більш явними.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.