Existe uma maneira de fornecer parâmetros nomeados em uma chamada de função em JavaScript?

207

Acho que o recurso de parâmetros nomeados em C # é bastante útil em alguns casos.

calculateBMI(70, height: 175);

O que posso usar se eu quiser isso em JavaScript?


O que eu não quero é isso:

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
}

Essa abordagem eu já usei. Existe outro caminho?

Estou bem usando qualquer biblioteca para fazer isso.

Robin Maben
fonte
Eu não acho que isso seja possível, mas você pode tentar colocar alguns indefinidos em lugares vazios. O que é muito ruim. Use o objeto, é bom.
Vladislav Qulin
14
Não, JavaScript / EcmaScript não suportam parâmetros nomeados. Desculpe.
precisa
1
Eu já sei disso. Obrigado. Eu estava procurando por uma maneira que envolve ajustar o que o existente Functionem javascript pode fazer.
Robin Maben
1
O existente Functionem Javascript não pode alterar a sintaxe principal do Javascript
Gareth '
2
Eu não acho que o Javascript suporta esse recurso. Eu acho que o mais próximo que você pode chegar dos parâmetros nomeados é (1) adicionar um comentário calculateBMI(70, /*height:*/ 175);, (2) fornecer um objeto calculateBMI(70, {height: 175})ou (3) usar uma constante const height = 175; calculateBMI(70, height);.
tfmontague

Respostas:

212

ES2015 e posterior

No ES2015, a destruição de parâmetros pode ser usada para simular parâmetros nomeados. Exigiria que o chamador passasse um objeto, mas você pode evitar todas as verificações dentro da função se também usar parâmetros padrão:

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

Existe uma maneira de chegar perto do que você deseja, mas é baseado na saída do Function.prototype.toString [ES5] , que depende da implementação em algum grau, portanto pode não ser compatível com vários navegadores.

A ideia é analisar os nomes dos parâmetros a partir da representação em cadeia da função, para que você possa associar as propriedades de um objeto ao parâmetro correspondente.

Uma chamada de função pode parecer

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

onde ae bsão argumentos posicionais e o último argumento é um objeto com argumentos nomeados.

Por exemplo:

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

Que você usaria como:

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

Existem algumas desvantagens nessa abordagem (você foi avisado!):

  • Se o último argumento for um objeto, ele será tratado como um "objeto de argumento nomeado"
  • Você sempre terá tantos argumentos quanto definiu na função, mas alguns deles podem ter o valor undefined(isso é diferente de não ter nenhum valor). Isso significa que você não pode usar arguments.lengthpara testar quantos argumentos foram passados.

Em vez de ter uma função criando o wrapper, você também pode ter uma função que aceite uma função e vários valores como argumentos, como

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

ou até estender Function.prototypepara que você possa fazer:

foo.execute(a, b, {posArg: ...});
Felix Kling
fonte
Sim ... aqui está um exemplo disso: jsfiddle.net/9U328/1 (embora você deva usar Object.definePropertye definir enumerablecomo false). Deve-se sempre ter cuidado ao estender objetos nativos. Toda a abordagem sente um hacky pouco, então eu não esperaria que ele funcione agora e para sempre;)
Felix Kling
Notado. Vou começar a usar isso. Marcando como resposta! ... Por enquanto;)
Robin Maben
1
Nitpick muito menor: eu não acho que essa abordagem pegue o EcmaScript 6 Arrow Functions . Atualmente, não é uma grande preocupação, mas vale a pena mencionar na seção de advertências de sua resposta.
precisa saber é o seguinte
3
@NobodyMan: Verdadeiro. Eu escrevi essa resposta antes que as funções das setas fossem importantes. Na ES6, eu realmente recorreria à destruição de parâmetros.
Felix Kling
1
Na questão de undefinedvs "sem valor", pode-se acrescentar que é exatamente assim que os valores padrão das funções JS estão funcionando - tratando undefinedcomo um valor ausente.
Dmitri Zaitsev
71

Não - a abordagem de objetos é a resposta do JavaScript para isso. Não há problema com isso, desde que sua função espere um objeto em vez de parâmetros separados.

Mitya
fonte
35
@RobertMaben - A resposta para a pergunta específica é que não existe uma maneira nativa de coletar vars ou funções declaradas sem saber que elas residem em um espaço para nome específico. Só porque a resposta é curta, ela não nega sua adequação como resposta - você não concorda? Existem respostas muito mais curtas por aí, na linha de "não, não é possível". Podem ser curtos, mas também são a resposta para a pergunta.
Mitya
3
Hoje em dia existe o es6: 2ality.com/2011/11/keyword-parameters.html
slacktracer
1
Esta é definitivamente uma resposta justa - 'parâmetros nomeados' é um recurso de linguagem. A abordagem de objetos é a próxima melhor coisa na ausência de um recurso desse tipo.
Fatuhoku
32

Esse problema tem sido um problema para mim há algum tempo. Sou um programador experiente, com muitas línguas em meu currículo. Um dos meus idiomas favoritos que tive o prazer de usar é o Python. O Python suporta parâmetros nomeados sem nenhum truque .... Desde que comecei a usar o Python (há algum tempo) tudo ficou mais fácil. Acredito que todo idioma deve suportar parâmetros nomeados, mas esse não é o caso.

Muitas pessoas dizem apenas usar o truque "Passar um objeto" para que você tenha nomeado parâmetros.

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

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

E isso funciona muito bem, exceto ... quando se trata da maioria dos IDEs existentes, muitos de nós desenvolvedores dependemos de dicas de tipo / argumento em nosso IDE. Eu pessoalmente uso o PHP Storm (junto com outros IDEs do JetBrains, como PyCharm para python e AppCode para Objective C)

E o maior problema ao usar o truque "Passar um objeto" é que, quando você está chamando a função, o IDE fornece uma dica de tipo único e é isso ... Como devemos saber quais parâmetros e tipos devem ser inseridos no objeto arg1?

Eu não tenho idéia de quais parâmetros devem ir no arg1

Então ... o truque "Passar um objeto" não funciona para mim ... Na verdade, causa mais dores de cabeça ao analisar o docblock de cada função antes que eu saiba quais parâmetros a função espera ... Claro, é ótimo para quando você está mantendo o código existente, mas é horrível escrever um novo código.

Bem, esta é a técnica que eu uso ... Agora, pode haver alguns problemas com ela, e alguns desenvolvedores podem me dizer que estou fazendo errado, e eu tenho uma mente aberta quando se trata dessas coisas ... Estou sempre disposto a procurar maneiras melhores de realizar uma tarefa ... Portanto, se houver um problema com essa técnica, os comentários serão bem-vindos.

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

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

Dessa forma, eu tenho o melhor dos dois mundos ... é fácil escrever um novo código, pois meu IDE me fornece todas as sugestões de argumentos adequadas ... E, mantendo o código posteriormente, posso ver de relance, não apenas o valor passado para a função, mas também o nome do argumento. A única sobrecarga que vejo é declarar os nomes dos seus argumentos como variáveis ​​locais para não poluir o espaço para nome global. Claro, é um pouco de digitação extra, mas trivial em comparação com o tempo necessário para procurar blocos de documentos enquanto escrevia um novo código ou mantinha o código existente.

Agora, tenho todos os parâmetros e tipos ao criar um novo código

Ray Perea
fonte
2
A única coisa com essa técnica é o fato de que você não pode alterar a ordem dos parâmetros ... Eu pessoalmente estou bem com isso.
22614 Ray Perea
13
Parece que isso está apenas causando problemas quando algum futuro mantenedor aparece e pensa que pode mudar a ordem dos argumentos (mas obviamente não pode).
ninguém
1
@AndrewMedico Concordo ... parece que você pode mudar a ordem dos argumentos como em Python. A única coisa que posso dizer sobre isso é que eles descobrirão rapidamente quando alterar a ordem dos argumentos interrompe o programa.
Raio Perea
7
Eu diria que myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');é melhor do que myFunc(arg1='Param1', arg2='Param2');, porque isso não tem nenhuma chance de enganar o leitor do que qualquer reais argumentos nomeados estão acontecendo
Eric
2
O padrão proposto não tem nada a ver com argumentos nomeados, a ordem ainda importa, os nomes não precisam ficar sincronizados com os parâmetros reais, o espaço para nome é poluído com variáveis ​​desnecessárias e estão implícitas relações que não existem.
Dmitri Zaitsev
25

Se você quiser deixar claro quais são os parâmetros, em vez de apenas chamar

someFunction(70, 115);

por que não fazer o seguinte

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

com certeza, é uma linha extra de código, mas vence na legibilidade.

dav_i
fonte
5
+1 por seguir o princípio do KISS, além de ajudar na depuração. No entanto, acho que cada var deve estar em sua própria linha, embora com um leve impacto no desempenho ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
Clairestreb
21
Não se trata apenas da linha extra de código, mas também da ordem dos argumentos e de torná-los opcionais. Então, você pode escrever isso com os parâmetros nomeados:, someFunction(height: 115);mas se você escrever, someFunction(height);estará definindo a largura.
Francisco Presencia
Nesse caso, o CoffeeScript suporta argumentos nomeados. Isso permitirá que você escreva apenas someFunction(width = 70, height = 115);. As variáveis ​​são declaradas no topo do escopo atual no código JavaScript gerado.
Audun Olsen
6

Outra maneira seria usar atributos de um objeto adequado, por exemplo:

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

Obviamente, para este exemplo inventado, é um tanto sem sentido. Mas nos casos em que a função se parece

do_something(some_connection_handle, some_context_parameter, some_value)

pode ser mais útil. Também poderia ser combinado com a idéia "parameterfy" para criar esse objeto a partir de uma função existente de uma maneira genérica. Isto é, para cada parâmetro, ele criaria um membro que pode ser avaliado como uma versão avaliada parcial da função.

Obviamente, essa ideia está relacionada ao Schönfinkeling, também conhecido como Currying.

Udo Klein
fonte
Essa é uma idéia interessante e se torna ainda melhor se você a combinar com os truques de introspecção de argumentos. Infelizmente, fica aquém de trabalhar com argumentos opcionais
Eric
2

Existe outro caminho. Se você estiver passando um objeto por referência, as propriedades desse objeto aparecerão no escopo local da função. Sei que isso funciona para o Safari (não verifiquei outros navegadores) e não sei se esse recurso tem um nome, mas o exemplo abaixo ilustra seu uso.

Embora na prática eu não pense que isso ofereça algum valor funcional além da técnica que você já está usando, é um pouco mais limpo semanticamente. E ainda requer a passagem de uma referência de objeto ou de um literal de objeto.

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));
Allen Ellison
fonte
2

Tentando o Nó-6.4.0 (process.versions.v8 = '5.0.71.60') e o Nó Chakracore-v7.0.0-pre8 e depois o Chrome-52 (V8 = 5.2.361.49), notei que os parâmetros nomeados são quase implementado, mas essa ordem ainda tem precedência. Não consigo encontrar o que diz o padrão 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

Mas a ordem é necessária !! É o comportamento padrão?

> f(b=10)
a=10 + b=2 = 12
jgran
fonte
10
Isso não está fazendo o que você pensa. O resultado da b=10expressão é 10, e é isso que é passado para a função. f(bla=10)também funcionaria (atribui 10 à variável blae passa o valor para a função posteriormente).
Matthias Kestenholz
1
Isso também está criando ae bvariáveis ​​no escopo global como efeito colateral.
Gershom
2

Função de chamada fcom parâmetros nomeados passados ​​como o objeto

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

está basicamente chamando sua composição f(...g(o))onde estou usando a sintaxe de propagação eg é um mapa de "ligação" que conecta os valores do objeto com suas posições de parâmetro.

O mapa de ligação é precisamente o ingrediente que falta, que pode ser representado pela matriz de suas chaves:

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

Observe os três ingredientes dissociados: a função, o objeto com argumentos nomeados e a ligação. Esse desacoplamento permite muita flexibilidade para usar essa construção, em que a ligação pode ser arbitrariamente personalizada na definição da função e arbitrariamente estendida no momento da chamada da função.

Por exemplo, você pode querer para abreviar heighte widthcomo he wdentro de definição da sua função, para torná-lo mais curto e mais limpa, enquanto você ainda quiser chamá-lo com nomes completos para maior clareza:

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

Essa flexibilidade também é mais útil para programação funcional, onde as funções podem ser arbitrariamente transformadas, perdendo seus nomes de parâmetros originais.

Dmitri Zaitsev
fonte
0

Vindo de Python, isso me incomodou. Eu escrevi um wrapper / proxy simples para o nó que aceitará objetos posicionais e de palavras-chave.

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

Vince Spicer
fonte
Se entendi corretamente, sua solução exige distinguir parâmetros posicionais e nomeados na definição da função, o que significa que não tenho liberdade para fazer essa escolha no momento da chamada.
Dmitri Zaitsev
@DmitriZaitsev: Na comunidade Python (com argumentos de palavra-chave sendo um recurso cotidiano), consideramos uma boa idéia poder forçar os usuários a especificar argumentos opcionais por palavra-chave; isso ajuda a evitar erros.
Tobias
@Tobias Forçar os usuários a fazê-lo em JS é um problema resolvido:, f(x, opt)onde optestá um objeto. Se isso ajuda a evitar ou a criar erros (como erros ortográficos e problemas para lembrar nomes de palavras-chave) permanece uma questão.
Dmitri Zaitsev
@DmitriZaitsev: No Python, isso evita estritamente erros, porque (é claro) os argumentos das palavras-chave são um recurso essencial da linguagem. Para argumentos somente de palavras-chave , o Python 3 possui uma sintaxe especial (enquanto no Python 2 você aperta as chaves do kwargs ditado uma a uma e, por fim, gera um TypeErrorse as chaves desconhecidas forem deixadas). Sua f(x, opt)solução permite que a ffunção faça algo como no Python 2, mas você precisaria lidar com todos os valores padrão.
Tobias
@Tobias Esta proposta é relevante para JS? Parece descrever como f(x, opt)já está funcionando, enquanto não vejo como isso ajuda a responder à pergunta, onde, por exemplo, você deseja fazer as duas coisas request(url)e request({url: url})isso não seria possível criando urlum parâmetro apenas de palavra-chave.
Dmitri Zaitsev
-1

Ao contrário do que geralmente se acredita, os parâmetros nomeados podem ser implementados no JavaScript padrão da velha escola (apenas para parâmetros booleanos) por meio de uma convenção de codificação simples e clara, como mostrado abaixo.

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

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

Ressalvas:

  • A ordem dos argumentos deve ser preservada - mas o padrão ainda é útil, pois torna óbvio qual argumento real se destina a qual parâmetro formal, sem a necessidade de grep para a assinatura da função ou usar um IDE.

  • Isso funciona apenas para booleanos. No entanto, tenho certeza de que um padrão semelhante poderia ser desenvolvido para outros tipos usando a semântica de coerção de tipo exclusivo do JavaScript.

user234461
fonte
Você ainda está se referindo por posição, não por nome aqui.
Dmitri Zaitsev
@DmitriZaitsev sim, eu até disse isso acima. No entanto, o objetivo dos argumentos nomeados é deixar claro para o leitor o que cada argumento significa; é uma forma de documentação. Minha solução permite que a documentação seja incorporada na chamada de função sem recorrer a comentários, que parecem desarrumados.
user234461
Isso resolve um problema diferente. A pergunta era sobre passar heightpelo nome, independentemente da ordem dos parâmetros.
Dmitri Zaitsev
@DmitriZaitsev, na verdade, a pergunta não disse nada sobre a ordem dos parâmetros ou por que o OP queria usar parâmetros nomeados.
user234461
Refere-se ao C #, onde a passagem de parâmetros por nome em qualquer ordem é a chave.
Dmitri Zaitsev