Qual a melhor forma de determinar se um argumento não é enviado para a função JavaScript


236

Eu já vi dois métodos para determinar se um argumento foi passado para uma função JavaScript. Gostaria de saber se um método é melhor que o outro ou se é apenas ruim de usar?

 function Test(argument1, argument2) {
      if (Test.arguments.length == 1) argument2 = 'blah';

      alert(argument2);
 }

 Test('test');

Ou

 function Test(argument1, argument2) {
      argument2 = argument2 || 'blah';

      alert(argument2);
 }

 Test('test');

Tanto quanto posso dizer, ambos resultam na mesma coisa, mas eu só usei o primeiro antes na produção.

Outra opção, como mencionado por Tom :

function Test(argument1, argument2) {
    if(argument2 === null) {
        argument2 = 'blah';
    }

    alert(argument2);
}

Conforme o comentário de Juan, seria melhor mudar a sugestão de Tom para:

function Test(argument1, argument2) {
    if(argument2 === undefined) {
        argument2 = 'blah';
    }

    alert(argument2);
}

É realmente o mesmo. Se você sempre tiver um número estático de argumentos, use o segundo método, caso contrário, provavelmente será mais fácil iterar usando a matriz de argumentos.
Luca Matteis

7
Um argumento que não foi passado é indefinido. O teste com igualdade estrita contra nulo falhará. Você deve usar igualdade estrita com indefinido.
Juan Mendes

19
Lembre-se: argument2 || 'blah';resultará em 'blá' se argument2for false(!), Não simplesmente se for indefinido. Se argument2for um booleano e a função for passada falsepara ele, essa linha retornará 'blá', apesar de argument2ser definida corretamente .
Sandy Gifford

9
@SandyGifford: O mesmo problema se argument2é 0, ''ou null.
rvighne

@rvighne Muito verdade. A interpretação única de Javascript de objetos e elenco é ao mesmo tempo as melhores e as piores partes.
Sandy Gifford

Respostas:


274

Existem várias maneiras diferentes de verificar se um argumento foi passado para uma função. Além das duas que você mencionou na sua pergunta (original) - verificando arguments.lengthou usando o ||operador para fornecer valores padrão - também é possível verificar explicitamente os argumentos por undefinedvia argument2 === undefinedou typeof argument2 === 'undefined'se é paranóico (consulte os comentários).

Usando o ||operador tornou-se prática padrão - todos os miúdos legal fazê-lo - mas tenha cuidado: o valor padrão será acionado se os avalia argumento para false, o que significa que pode realmente ser undefined, null, false, 0, ''(ou qualquer outra coisa para a qual Boolean(...)retorna false).

Portanto, a questão é quando usar essa verificação, pois todas elas produzem resultados ligeiramente diferentes.

A verificação arguments.lengthexibe o comportamento 'mais correto', mas pode não ser possível se houver mais de um argumento opcional.

O teste para undefinedé o próximo 'melhor' - apenas 'falha' se a função for chamada explicitamente com um undefinedvalor, que provavelmente deve ser tratado da mesma maneira que omitir o argumento.

O uso do ||operador pode acionar o uso do valor padrão, mesmo que um argumento válido seja fornecido. Por outro lado, seu comportamento pode realmente ser desejado.

Para resumir: use-o apenas se você souber o que está fazendo!

Na minha opinião, usar ||também é o caminho a percorrer se houver mais de um argumento opcional e não se desejar passar um literal de objeto como uma solução alternativa para os parâmetros nomeados.

Outra boa maneira de fornecer valores padrão arguments.lengthé possível através dos rótulos de uma instrução switch:

function test(requiredArg, optionalArg1, optionalArg2, optionalArg3) {
    switch(arguments.length) {
        case 1: optionalArg1 = 'default1';
        case 2: optionalArg2 = 'default2';
        case 3: optionalArg3 = 'default3';
        case 4: break;
        default: throw new Error('illegal argument count')
    }
    // do stuff
}

Isso tem o lado negativo de que a intenção do programador não é (visualmente) óbvia e usa 'números mágicos'; é, portanto, possivelmente propenso a erros.


42
Você deve realmente verificar o tipo de argumento2 === "indefinido", caso alguém defina "indefinido".
JW.

133
Vou acrescentar um aviso - mas que bastardos doentes fazem coisas assim?
Christoph

12
undefined é uma variável no espaço global. É mais lento procurar essa variável no escopo global do que uma variável em um escopo local. Mas o mais rápido é usar typeof x === "indefinido"
algum dia

5
Interessante. Por outro lado, a comparação === indefinida pode ser mais rápida que a comparação de cadeias. Meus testes parecem indicar que você está certa, no entanto: x === indefinido necessária ~ 1,5x o tempo de typeof x === 'indefinido'
Christoph

4
@ Cristoph: depois de ler seu comentário, perguntei por aí. Pude provar que a comparação de strings certamente não é (apenas) por comparação de ponteiros, pois comparar uma string gigantesca leva mais tempo do que uma string pequena. No entanto, comparar duas seqüências gigantescas é realmente lento apenas se elas foram criadas com concatenação, mas muito rápido se a segunda foi criada como uma atribuição da primeira. Por isso, provavelmente funciona por um teste ponteiro seguido de letra por letra testar Então, sim, eu estava cheio de porcaria :) obrigado por me esclarecer ...
Juan Mendes

17

Se você estiver usando o jQuery, uma opção interessante (especialmente para situações complicadas) é usar o método de extensão do jQuery .

function foo(options) {

    default_options = {
        timeout : 1000,
        callback : function(){},
        some_number : 50,
        some_text : "hello world"
    };

    options = $.extend({}, default_options, options);
}

Se você chamar a função, faça o seguinte:

foo({timeout : 500});

A variável options seria então:

{
    timeout : 500,
    callback : function(){},
    some_number : 50,
    some_text : "hello world"
};

15

Este é um dos poucos casos em que encontro o teste:

if(! argument2) {  

}

funciona muito bem e carrega a implicação correta sintaticamente.

(Com a restrição simultânea de que eu não permitiria um valor nulo legítimo para o argument2qual tenha algum outro significado; mas isso seria realmente confuso.)

EDITAR:

Este é realmente um bom exemplo de diferença estilística entre linguagens de tipo fraco e forte; e uma opção estilística que o javascript oferece em espadas.

Minha preferência pessoal (sem críticas para outras preferências) é minimalismo. Quanto menos o código tiver a dizer, desde que eu seja consistente e conciso, menos alguém precisará compreender para inferir corretamente meu significado.

Uma implicação dessa preferência é que eu não quero - não acho útil - acumular vários testes de dependência de tipo. Em vez disso, tento fazer com que o código signifique o que parece; e teste apenas o que realmente preciso testar.

Um dos agravos que encontro no código de outras pessoas está precisando descobrir se eles esperam ou não, em um contexto mais amplo, realmente encontrar os casos pelos quais estão testando. Ou se eles estão tentando testar o máximo possível, com a chance de não antecipar o contexto completamente o suficiente. O que significa que acabo precisando rastreá-los exaustivamente nas duas direções antes de poder refatorar ou modificar qualquer coisa com confiança. Acho que há uma boa chance de que eles possam ter implementado esses vários testes porque previram circunstâncias onde seriam necessárias (e que geralmente não são aparentes para mim).

(Considero isso uma desvantagem séria na maneira como essas pessoas usam linguagens dinâmicas. Muitas vezes as pessoas não querem desistir de todos os testes estáticos e acabam fingindo.)

Eu já vi isso de maneira flagrante ao comparar o código abrangente do ActionScript 3 com o elegante código javascript. O AS3 pode ser 3 ou 4 vezes a maior parte dos js, e a confiabilidade que eu suspeito é pelo menos não melhor, apenas por causa do número (3-4X) de decisões de codificação que foram tomadas.

Como você diz, Shog9, YMMV. : D


1
if (! argument2) argument2 = 'default' é equivalente a argument2 = argument2 || 'default' - Acho que a segunda versão visualmente mais agradável ...
Christoph

5
E acho isso mais detalhado e perturbador; mas é preferência pessoal, tenho certeza.
dkretz

4
@le dorfier: também impede o uso de strings vazias, 0 e boolean false.
Shog9

@le dorfier: além da estética, há uma diferença importante: a última cria efetivamente um segundo caminho de execução, que pode tentar mantenedores descuidados a adicionar comportamento além da simples atribuição de um valor padrão. YMMV, é claro.
Shog9

3
what if parameter2 é um booleano === false; ou uma função {return false;}?
Fresko

7

Existem diferenças significativas. Vamos configurar alguns casos de teste:

var unused; // value will be undefined
Test("test1", "some value");
Test("test2");
Test("test3", unused);
Test("test4", null);
Test("test5", 0);
Test("test6", "");

Com o primeiro método que você descreve, apenas o segundo teste usa o valor padrão. O segundo método será o padrão todos, mas o primeiro (como JS irá converter undefined, null, 0, e ""para o boolean false. E se você fosse usar o método de Tom, apenas o quarto teste usará o padrão!

O método escolhido depende realmente do comportamento pretendido. Se outros valores undefinedsão permitidos argument2, provavelmente você desejará alguma variação no primeiro; se um valor diferente de zero, nulo e não vazio é desejado, o segundo método é ideal - na verdade, ele é frequentemente usado para eliminar rapidamente uma ampla variedade de valores da consideração.


7
url = url === undefined ? location.href : url;

Ossos nus respondem. Alguma explicação não faria mal.
Paul Rooney

É um operador ternário que diz concisa: Se o URL estiver indefinido (ausente), defina a variável url como location.href (a página da Web atual), caso contrário, defina a variável url como o URL definido.
rmooney

5

No ES6 (ES2015), você pode usar Parâmetros padrão

function Test(arg1 = 'Hello', arg2 = 'World!'){
  alert(arg1 + ' ' +arg2);
}

Test('Hello', 'World!'); // Hello World!
Test('Hello'); // Hello World!
Test(); // Hello World!


Esta resposta é realmente interessante e pode ser útil para op. Embora ele realmente não responde a pergunta
Ulysse BN

A meu ver - ele quer definir arg se não foi definido, então eu postei algumas informações úteis
Andriy2

O que quero dizer é que você não está respondendo à melhor forma de determinar se um argumento não é enviado à função JavaScript . Mas a pergunta pode ser respondida usando argumentos padrão: por exemplo, nomeando você argumentos "default value"e verificando se o valor é realmente "default value".
Ulysse BN

4

Sinto muito, ainda não posso comentar, então, para responder à resposta de Tom ... Em javascript (indefinido! = Null) == false Na verdade, essa função não funcionará com "null", você deve usar "undefined"


1
E Tom recebeu dois votos positivos por uma resposta errada - é sempre bom saber o quão bom esses sistemas comunitários funcionam;)
Christoph

3

Por que não usar o !!operador? Esse operador, colocado antes da variável, transforma-o em um booleano (se eu entendi bem), então !!undefinede !!null(e até !!NaN, o que pode ser bem interessante) retornará false.

Aqui está um exemplo:

function foo(bar){
    console.log(!!bar);
}

foo("hey") //=> will log true

foo() //=> will log false

Não funciona com string booleana true, zero e vazia. Por exemplo, foo (0); registrará falso, mas foo (1) registrará verdade
rosell.dk

2

Pode ser conveniente abordar a detecção de argumentos, evocando sua função com um Objeto de propriedades opcionais:

function foo(options) {
    var config = { // defaults
        list: 'string value',
        of: [a, b, c],
        optional: {x: y},
        objects: function(param){
           // do stuff here
        }
    }; 
    if(options !== undefined){
        for (i in config) {
            if (config.hasOwnProperty(i)){
                if (options[i] !== undefined) { config[i] = options[i]; }
            }
        }
    }
}

2

Também há uma maneira complicada de descobrir se um parâmetro é passado para uma função ou não . Veja o exemplo abaixo:

this.setCurrent = function(value) {
  this.current = value || 0;
};

Isso necessário significa que, se o valor de valuenão estiver presente / passado - defina-o como 0.

Muito legal né!


1
Na verdade, isso significa "se valuefor equivalente a false, defina-o como 0." Essa é uma distinção sutil, mas muito importante.
Charles Wood

@CharlesWood O valor não passou / o meio atual é falso apenas #
Mohammed Zameer 09/02

1
Claro, se isso se encaixa no requisito de sua função. Mas se, por exemplo, seu parâmetro for booleano, truee falseforem valores válidos, convém ter um terceiro comportamento para quando o parâmetro não for passado (especialmente se a função tiver mais de um parâmetro).
Charles Wood

1
E devo reconhecer que esta é uma grande discussão em ciência da computação e pode acabar sendo uma questão de opinião: D
Charles Wood

@CharlesWood desculpe pelo atraso na festa. Eu sugiro que você adicione-os na própria resposta com opção de edição
Mohammed Zameer

1

Algumas vezes você também pode querer verificar o tipo, especialmente se estiver usando a função como getter e setter. O código a seguir é ES6 (não será executado no EcmaScript 5 ou mais antigo):

class PrivateTest {
    constructor(aNumber) {
        let _aNumber = aNumber;

        //Privileged setter/getter with access to private _number:
        this.aNumber = function(value) {
            if (value !== undefined && (typeof value === typeof _aNumber)) {
                _aNumber = value;
            }
            else {
                return _aNumber;
            }
        }
    }
}

0
function example(arg) {
  var argumentID = '0'; //1,2,3,4...whatever
  if (argumentID in arguments === false) {
    console.log(`the argument with id ${argumentID} was not passed to the function`);
  }
}

Porque matrizes são herdadas de Object.prototype. Considere ⇑ para tornar o mundo melhor.


-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

Se a função acima for chamada de muitos lugares e você tiver certeza de que os 2 primeiros parâmetros foram passados ​​de todos os locais, mas não tiver certeza do terceiro parâmetro, poderá usar a janela.

window.param3 tratará se não for definido no método de chamada.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.