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 a
e b
sã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.length
para 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.prototype
para que você possa fazer:
foo.execute(a, b, {posArg: ...});