Есть ли способ предоставить именованные параметры в вызове функции в 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

Ответы:

213

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 . В настоящее время это не большая проблема, но, возможно, стоит упомянуть в разделе «Предостережения» вашего ответа.
NobodyMan
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 (наряду с другими средами разработки 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
Единственная вещь с этой техникой - тот факт, что вы не можете изменить порядок параметров ... Я лично с этим согласен.
Рэй Переа
14
Похоже, что это просто напрашивается на неприятности, когда приходит какой-то будущий сопровождающий и думает, что может изменить порядок аргументов (но, очевидно, не может).
никто не
1
@ AndrewMedico Я согласен ... похоже, вы можете просто изменить порядок аргументов, как в Python. Единственное, что я могу сказать по этому поводу - они узнают очень быстро, когда изменение порядка аргументов нарушает программу.
Рэй Переа
7
Я бы сказал, что myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');это лучше, чем myFunc(arg1='Param1', arg2='Param2');потому, что у читателя нет шансов обмануть себя, чем происходят какие-либо настоящие именованные споры
Эрик
3
Предложенный шаблон не имеет ничего общего с именованными аргументами, порядок все еще имеет значение, имена не должны синхронизироваться с фактическими параметрами, пространство имен загрязнено ненужными переменными, и подразумеваются отношения, которых там нет.
Дмитрий Зайцев
25

Если вы хотите прояснить, каков каждый из параметров, а не просто вызывать

someFunction(70, 115);

почему бы не сделать следующее

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

конечно, это дополнительная строка кода, но она выигрывает по удобству чтения.

dav_i
источник
5
+1 за следование принципу KISS, плюс это также помогает при отладке. Тем не менее, я думаю, что каждая переменная должна быть отдельной строкой, хотя и с небольшим снижением производительности ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
Clairestreb
21
Речь идет не только о дополнительной строке кода, но и о порядке аргументов и их необязательности. Таким образом, вы можете написать это с именованными параметрами:, someFunction(height: 115);но если вы пишете, someFunction(height);вы фактически устанавливаете ширину.
Франсиско Презенсия
В этом случае 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)

это может быть более полезным. Это также может быть объединено с идеей «parameterfy» для создания такого объекта из существующей функции универсальным способом. Это значит, что для каждого параметра будет создан член, который может оценивать версию функции с частичной оценкой.

Эта идея, конечно, связана с Schönfinkeling aka Curry.

Удо Кляйн
источник
Это изящная идея, и она становится еще лучше, если вы объедините ее с приемами самоанализа аргументов. К сожалению, он не работает с необязательными аргументами
Эрик
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
jgran
источник
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 dict один за другим и, наконец, поднимаете, 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.

user234461
источник
Вы по-прежнему ссылаетесь на должность, а не на имя здесь.
Дмитрий Зайцев
@DmitriZaitsev да я и так сказал выше. Однако цель именованных аргументов состоит в том, чтобы разъяснить читателю, что означает каждый аргумент; это форма документации. Мое решение позволяет встраивать документацию в вызов функции, не прибегая к комментариям, которые выглядят неопрятно.
user234461
Это решает другую проблему. Речь шла о передаче heightпо имени независимо от порядка param.
Дмитрий Зайцев
@DmitriZaitsev на самом деле, вопрос ничего не говорил о порядке параметров, или почему OP хотел использовать именованные параметры.
user234461
Это происходит путем обращения к C #, где ключом является передача параметров по имени в любом порядке.
Дмитрий Зайцев