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

Я считаю функцию именованных параметров в С# весьма полезной в некоторых случаях.

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
}

Такой подход я уже использовал. Есть ли другой способ?

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

Ответ 1

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

Ответ 2

Нет - объектный подход - это ответ на JavaScript. Нет проблем с этим, если ваша функция ожидает объект, а не отдельные параметры.

Ответ 3

Эта проблема была моей любимой мозолью в течение некоторого времени. Я опытный программист со многими языками под моим поясом. Один из моих любимых языков, которым я имел удовольствие использовать 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?

I have no idea what parameters should go in arg1

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

Ну, это техника, которую я использую... Теперь могут быть некоторые проблемы с ней, и некоторые разработчики могут сказать мне, что я делаю это неправильно, и у меня есть открытый ум, когда дело доходит до этих вещей... Я всегда готов взглянуть на лучшие способы решения задачи... Итак, если есть проблема с этой техникой, то комментарии приветствуются.

/**
 * 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 дает мне все правильные подсказки аргументов... И, сохраняя код позже, я могу сразу увидеть, не только значение, переданное функции, но и имя аргумента. Единственный недостаток, который я вижу, - объявлять ваши имена аргументов как локальные переменные, чтобы не загрязнять глобальное пространство имен. Конечно, это немного лишний набор текста, но тривиальный по сравнению с временем, необходимым для поиска докблоков при написании нового кода или сохранении существующего кода.

Now, I have all the parameters and types when creating new code

Ответ 4

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

someFunction(70, 115);

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

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

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

Ответ 5

bob.js (написанный мной) может преобразовать вашу регулярную функцию в ту, которая принимает именованные аргументы, в одной строке:

func = bob.fn.namedArgFunction(func); 

ПОДРОБНЕЕ:

Я думаю, что некоторым читателям может понадобиться разъяснение по вышеуказанному решению. Итак, вот недостающая часть.

Определение функции будет принимать регулярные аргументы (а не объект), поэтому у вас будет ясный код для выявления намерений:

var func = function(a, b) {
    return a + b;
}

Без вызова bob.js вызывающему абоненту необходимо передать a и b в качестве обычных аргументов. Однако, если вы выполните код, предоставленный в качестве исходного решения:

func = bob.fn.namedArgFunction(func);

Затем вы можете передать объект в качестве аргумента вместо a и b:

var arg = { a: 1, b: 2 };
var sum = func(arg); //sum is 3 (=1+2).

ПРИМЕЧАНИЕ: это решение отлично работает без минимизации. Однако, если вы уменьшаете свои функции javascript, тогда имена аргументов функции будут изменены во время мини-кодирования, поэтому это решение не поможет. Я думаю, мне может понадобиться реализовать шаблон, похожий на angular (у него есть для инъекции зависимостей), чтобы явно упоминать имена аргументов. Но на сегодняшний день это решение (как и некоторые другие, представленные в этой статье) не является гибким для минимизации.

Ответ 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 Currying.

Ответ 7

Есть и другой способ. Если вы передаете объект по ссылке, эти свойства объекта появятся в локальной области функций. Я знаю, что это работает для 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));

Ответ 8

Попытка Node -6.4.0 (process.versions.v8 = '5.0.71.60') и Node Chakracore-v7.0.0-pre8, а затем Chrome-52 (V8 = 5.2.361.49), я ' ve заметил, что именованные параметры почти реализованы, но этот порядок все еще имеет приоритет. Я не могу найти то, что говорит стандарт 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

Ответ 9

Исходя из Python, это меня раздражало. Я написал простую оболочку /Proxy для узла, которая будет исключать как позиционные, так и ключевые объекты.

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

Ответ 10

Вызов функции 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})

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

Ответ 11

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

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

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

Предостережения:

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

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