Чи існує спосіб надання названих параметрів у виклику функції в JavaScript?


207

Я вважаю, що названа параметри в C # є досить корисною в деяких випадках.

calculateBMI(70, height: 175);

Що я можу використовувати, якщо це потрібно в JavaScript?


Я не хочу цього:

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

Такий підхід я вже використовував. Чи є інший спосіб?

Я добре використовую будь-яку бібліотеку для цього.


Я не думаю, що це можливо, але ви можете спробувати поставити деякі невизначені місця в порожніх місцях. Який спосіб поганий. Використовуйте предмет, його благо.
Владислав Кулін

14
Ні, JavaScript / EcmaScript не підтримують названі параметри. Вибачте.
smilly92

1
Я це вже знаю. Дякую. Я шукав якийсь спосіб, який передбачає налаштування того, що Functionможна зробити в JavaScript.
Робін Мабен

1
Існуючий Functionв Javascript не може змінити основний синтаксис Javascript
Гарет

2
Я не думаю, що Javascript підтримує цю функцію. Я думаю, що найближчим до вас можна назвати названі параметри (1) додати коментар calculateBMI(70, /*height:*/ 175);, (2) надати об’єкт calculateBMI(70, {height: 175})або (3) використовувати константу const height = 175; calculateBMI(70, height);.
tfmontague

Відповіді:


211

ES2015 та новіших версій

У ES2015 деструктуризація параметрів може використовуватися для імітації названих параметрів. Буде потрібно, щоб абонент передав об'єкт, але ви можете уникнути всіх перевірок всередині функції, якщо ви також використовуєте параметри за замовчуванням:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

Існує спосіб наблизитись до того, що ви хочете, але він базується на результатах Function.prototype.toString [ES5] , який певною мірою залежить від реалізації, тому він може бути не сумісним між браузерами.

Ідея полягає в тому, щоб проаналізувати імена параметрів з рядкового представлення функції, щоб ви могли пов'язати властивості об'єкта з відповідним параметром.

Виклик функції може виглядати таким чином

func(a, b, {someArg: ..., someOtherArg: ...});

де aі bє позиційні аргументи, а останній аргумент - це об'єкт з названими аргументами.

Наприклад:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

Що б ви використовували як:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

DEMO

У цьому підході є деякі недоліки (вас попереджали!):

  • Якщо останній аргумент є об'єктом, він трактується як "названий об'єкт аргументу"
  • Ви завжди отримаєте стільки аргументів, скільки ви визначили у функції, але деякі з них можуть мати значення undefined(це відмінне від взагалі ніякого значення). Це означає, що ви не можете використовувати arguments.lengthдля перевірки, скільки аргументів було передано.

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

call(func, a, b, {posArg: ... });

або навіть подовжити Function.prototypeтак, щоб ви могли:

foo.execute(a, b, {posArg: ...});

Так ... ось приклад для цього: jsfiddle.net/9U328/1 (хоча ви повинні краще використовувати Object.definePropertyі набір enumerableдля false). Завжди слід бути обережними, коли розширюєте рідні об’єкти. Весь підхід відчуває себе трохи хитким, тому я б не очікував, що він буде працювати зараз і назавжди;)
Фелікс Клінг

Помічено. Я перейду до використання цього. Позначення як відповідь! ... Поки що;)
Робін Мабен

1
Дуже незначна нітрик: я не думаю, що цей підхід охопить функції стрілок EcmaScript 6 . Зараз це не викликає особливих проблем, але, можливо, варто згадати у розділі про застереження вашої відповіді.
НіхтоМань

3
@NobodyMan: Правда. Я написав цю відповідь, перш ніж функції стрілок були річчю. У ES6 я б фактично вдався до деструктуризації параметрів.
Фелікс Клінг

1
Що стосується питання щодо undefined"немає значення", можна додати, що саме так працюють стандартні значення JS-функцій - трактуючи undefinedяк відсутнє значення.
Дмитро Зайцев

71

Ні - об’єктний підхід - це відповідь JavaScript на це. З цим немає жодної проблеми, якщо ваша функція очікує об'єкт, а не окремі параметри.


35
@RobertMaben - Відповідь на конкретне запитане запитання полягає в тому, що немає власного способу збирання оголошених змін або функцій, не знаючи, що вони живуть у певному просторі імен. Тільки тому, що відповідь коротка, вона не заперечує її придатності як відповіді - не погодились би ви? Є набагато коротші відповіді там, відповідно до "ні, не можливо". Вони можуть бути короткими, але вони також є відповіддю на питання.
Митя

3
Сьогодні є es6, хоча: 2ality.com/2011/11/keyword-parameters.html
slacktracer

1
Це, безумовно, справедлива відповідь - "названі параметри" є мовною особливістю. Об'єктний підхід - наступне найкраще за відсутності такої особливості.
фатухоку

32

Це питання вже певний час є моїм вихованцем. Я досвідчений програміст з багатьма мовами під поясом. Одна з моїх улюблених мов, якою я мав задоволення користуватися - Python. Python підтримує іменовані параметри без жодних хитрощів. З тих пір, як я почав використовувати Python (деякий час тому), все стало простіше. Я вважаю, що кожна мова повинна підтримувати названі параметри, але це просто не так.

Багато людей кажуть просто використовувати трюк "Передати об'єкт", щоб ви назвали параметри.

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

І це чудово, за винятком ..... коли мова йде про більшість IDE там, багато з нас розробники покладаються на підказки типу / аргументів у нашій IDE. Я особисто використовую PHP Storm (поряд з іншими IDE JetBrains, такими як PyCharm для python та AppCode для Objective C)

І найбільша проблема використання трюку "Передати об'єкт" полягає в тому, що, коли ви викликаєте функцію, IDE дає вам підказку одного типу, і це все ... Як ми повинні знати, які параметри та типи повинні входити в об’єкт arg1?

Я поняття не маю, які параметри повинні йти в arg1

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

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

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

Таким чином, у мене є найкраще з обох світів ... новий код легко написати, оскільки мій IDE дає мені всі належні натяки на аргументи ... І, зберігаючи код згодом, я можу побачити не лише значення, передане функції, але також назва аргументу. Єдине, що я бачу, - це оголошення ваших аргументів як локальних змінних, щоб не забруднювати глобальний простір імен. Звичайно, це трохи зайвого набору тексту, але банально порівняно з часом, необхідним для пошуку докблоків під час написання нового коду чи підтримки існуючого коду.

Тепер у мене є всі параметри та типи при створенні нового коду


2
Єдине, що в цій техніці - це те, що ви не можете змінити порядок параметрів ... Я особисто з цим добре.
Ray Perea

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

1
@AndrewMedico Я згоден ... схоже, ви можете просто змінити порядок аргументів, як у Python. Єдине, про що я можу сказати, це те, що вони дізнаються реально швидко, коли зміна порядку аргументу порушує програму.
Ray Perea

7
Я б заперечував, що myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');це краще myFunc(arg1='Param1', arg2='Param2');, тому що це не має шансів підманути читача, ніж трапляються будь-які фактично названі аргументи
Ерік

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

25

Якщо ви хочете, щоб було зрозуміло, що таке кожен з параметрів, а не просто виклик

someFunction(70, 115);

чому б не зробити наступне

var width = 70, height = 115;
someFunction(width, height);

звичайно, це додатковий рядок коду, але він виграє на читанні.


5
+1 для дотримання принципу KISS, а також допомагає при налагодженні. Однак я вважаю, що кожен var повинен знаходитись у власному рядку, хоча і з невеликим враженням від продуктивності ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
clairestreb

21
Йдеться не лише про додатковий рядок коду, це також про порядок аргументів та надання їм необов’язкових. Таким чином, ви можете написати це з названими параметрами:, someFunction(height: 115);але якщо ви пишете, someFunction(height);ви фактично встановлюєте ширину.
Francisco Presencia

У цьому випадку CoffeeScript підтримує названі аргументи. Це дозволить писати просто someFunction(width = 70, height = 115);. Змінні оголошуються у верхній частині поточної області в коді JavaScript, який генерується.
Audun Olsen

6

Іншим способом було б використання атрибутів відповідного об'єкта, наприклад, так:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

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

do_something(some_connection_handle, some_context_parameter, some_value)

це може бути корисніше. Це також може бути поєднане з ідеєю "параметру", щоб створити такий об'єкт із існуючої функції загальним способом. Тобто для кожного параметра він створив би члена, який може оцінювати до частково оціненої версії функції.

Ця ідея, звичайно, пов'язана з Schönfinkeling aka Currying.


Це ідеальна ідея, і стає ще кращою, якщо поєднувати її з хитрощами з самоаналізу аргументів. На жаль, не вистачає роботи з факультативними аргументами
Ерік,

2

Є й інший спосіб. Якщо ви передаєте об'єкт за посиланням, його властивості відобразяться у локальній області функції. Я знаю, що це працює для Safari (не перевіряли інші браузери), і я не знаю, чи має ця функція ім'я, але наведений нижче приклад ілюструє її використання.

Хоча на практиці я не думаю, що це надає будь-яку функціональну цінність, ніж техніка, яку ви вже використовуєте, вона є семантично чистішою. І це все ще вимагає передачі посилання на об'єкт або літералу на об'єкт.

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));

2

Спробуючи Node-6.4.0 (process.versions.v8 = '5.0.71.60') та Node Chakracore-v7.0.0-pre8, а потім Chrome-52 (V8 = 5.2.361.49), я помітив, що названі параметри майже здійснено, але цей порядок все ще має перевагу. Я не можу знайти те, що говорить стандарт ECMA.

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

Але замовлення потрібно !! Це стандартна поведінка?

> f(b=10)
a=10 + b=2 = 12

10
Це не те, що ти думаєш. Результат b=10виразу є 10, і саме це передається функції. f(bla=10)також буде працювати (він присвоює 10 змінній blaі передає значення функції після цього).
Маттіас Кестенхольц

1
Це також створює aі bзмінні в глобальному масштабі як побічний ефект.
Гершом

2

Функція виклику fз названими параметрами передається як об'єкт

o = {height: 1, width: 5, ...}

в основному називає його склад f(...g(o))там, де я використовую синтаксис розповсюдження і gявляє собою "прив'язку" карти, що з'єднує значення об'єкта з їх положеннями параметрів.

Карта зв’язування - це саме той відсутній інгредієнт, який може бути представлений масивом його ключів:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

Зверніть увагу на три роз'єднані інгредієнти: функцію, об’єкт із названими аргументами та прив’язку. Ця розв'язка забезпечує велику гнучкість для використання цієї конструкції, де прив'язка може бути довільно налаштована у визначенні функції та довільно розширена на час виклику функції.

Наприклад, ви можете скоротити heightі widthяк, hі wвсередині визначення функції, щоб зробити її коротшою і більш чистою, тоді як ви все ще хочете називати її з повними іменами для наочності:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

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


0

Походячи з Python, це набридло мені. Я написав просту обгортку / проксі для вузла, який буде приймати як позиційні, так і ключові об’єкти.

https://github.com/vinces1979/node-def/blob/master/README.md


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

@DmitriZaitsev: У спільноті Python (аргументи ключових слів є повсякденною ознакою) ми вважаємо дуже гарною ідеєю можливість змусити користувачів визначати необов'язкові аргументи за ключовими словами; це допомагає уникнути помилок.
Тобіас

@Tobias Примушуючи користувачів робити це в JS - це вирішена проблема: f(x, opt)де optоб'єкт. Чи допомагає це уникнути або створити помилки (наприклад, спричинені неправильним написанням та болем запам'ятати назви ключових слів), залишається питанням.
Дмитро Зайцев

@DmitriZaitsev: У Python це суворо уникає помилок, тому що (звичайно) аргументи ключових слів є основною мовою. Для аргументів, що стосуються лише ключових слів , Python 3 має спеціальний синтаксис (тоді як у Python 2 ви кладете ключі з kwargs диктату по черзі та нарешті піднімаєте a, TypeErrorякщо невідомі ключі залишаються). Ваше f(x, opt)рішення дозволяє в fфункції зробити що - щось подібне в Python 2, але вам потрібно буде обробляти всі значення по замовчуванням самостійно.
Тобіас

@Tobias Чи актуальна ця пропозиція для JS? Здається, описується, як f(x, opt)це вже працює, в той час як я не бачу, як це допомагає відповісти на питання, де, наприклад, ви хочете зробити те і інше, request(url)і request({url: url})це було б неможливо, створивши urlпараметр лише для ключового слова.
Дмитро Зайцев

-1

На відміну від загальноприйнятої думки, названі параметри можуть бути реалізовані у стандартному, старому шкільному JavaScript (лише для булевих параметрів) за допомогою простого акуратного режиму кодування, як показано нижче.

function f(p1=true, p2=false) {
    ...
}

f(!!"p1"==false, !!"p2"==true); // call f(p1=false, p2=true)

Застереження:

  • Впорядкування аргументів повинно бути збережене - але шаблон все-таки корисний, оскільки з нього стає очевидним, який фактичний аргумент призначений для якого формального параметра, без необхідності грепатись за підпис функції або використовувати IDE.

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


Ви все ще посилаєтесь на посаду, а не на ім'я.
Дмитро Зайцев

@DmitriZaitsev так, я навіть сказав це вище. Однак мета названих аргументів - дати зрозуміти читачеві, що означає кожен аргумент; це форма документації. Моє рішення дозволяє вбудовувати документацію в функціональний виклик, не вдаючись до коментарів, які виглядають неохайними.
user234461

Це вирішує іншу проблему. Питання полягало у переході heightна прізвище незалежно від порядку парам.
Дмитро Зайцев

@DmitriZaitsev, фактично, питання нічого не говорило про порядок порядку, або чому ОП хотів використовувати названі парами.
user234461

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