La mejor manera de determinar si un argumento no se envía a la función de JavaScript

236

Ahora he visto 2 métodos para determinar si se ha pasado un argumento a una función de JavaScript. Me pregunto si un método es mejor que el otro o si uno es malo de usar.

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

      alert(argument2);
 }

 Test('test');

O

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

      alert(argument2);
 }

 Test('test');

Por lo que puedo decir, ambos resultan en lo mismo, pero solo he usado el primero antes en producción.

Otra opción mencionada por Tom :

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

    alert(argument2);
}

Según el comentario de Juan, sería mejor cambiar la sugerencia de Tom para:

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

    alert(argument2);
}
Darryl Hein
fuente
Es realmente lo mismo. Si siempre tiene un número estático de argumentos, vaya con el segundo método, de lo contrario, probablemente sea más fácil iterar usando la matriz de argumentos.
Luca Matteis el
77
Un argumento que no se aprobó entra como indefinido. La prueba con igualdad estricta contra nulo fallará. Debe usar la igualdad estricta con indefinido.
Juan Mendes
19
Recuerde: argument2 || 'blah';dará como resultado 'blah' si argument2es false(!), No simplemente si no está definido. Si argument2es un booleano y se le pasa la función false, esa línea devolverá 'blah' a pesar de argument2estar definida correctamente .
Sandy Gifford
99
@SandyGifford: El mismo problema si argument2es 0, ''o null.
rvighne
@rvighne Muy cierto. La interpretación única de Javascript de los objetos y el casting es a la vez lo mejor y lo peor.
Sandy Gifford

Respuestas:

274

Hay varias formas diferentes de verificar si se pasó un argumento a una función. Además de los dos que mencionó en su pregunta (original), verificando arguments.lengtho usando el ||operador para proporcionar valores predeterminados, uno también puede verificar explícitamente los argumentos para undefinedvía argument2 === undefinedotypeof argument2 === 'undefined' si uno es paranoico (ver comentarios).

Usando el ||operador ha convertido en una práctica estándar - todos los niños frescos lo hacen - pero tenga cuidado: El valor por defecto se activa si los argumentos evalúa a false, lo que significa que en realidad podría ser undefined, null, false, 0, ''(o cualquier otra cosa para la que Boolean(...)vuelvefalse ).

Entonces, la pregunta es cuándo usar qué verificación, ya que todas producen resultados ligeramente diferentes.

Comprobación arguments.length exhibe el comportamiento 'más correcto', pero podría no ser factible si hay más de un argumento opcional.

La prueba para undefinedes el próximo 'mejor': solo 'falla' si la función se llama explícitamente con unundefined valor, que probablemente debería tratarse de la misma manera que omitiendo el argumento.

El uso de la || operador puede desencadenar el uso del valor predeterminado incluso si se proporciona un argumento válido. Por otro lado, su comportamiento podría ser realmente deseado.

Para resumir: ¡Úselo solo si sabe lo que está haciendo!

En mi opinión, el uso ||también es el camino a seguir si hay más de un argumento opcional y uno no quiere pasar un objeto literal como solución para los parámetros con nombre.

Otra buena manera de proporcionar valores predeterminados usando arguments.lengthes posible cayendo a través de las etiquetas de una declaración de cambio:

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
}

Esto tiene el inconveniente de que la intención del programador no es (visualmente) obvia y utiliza 'números mágicos'; Por lo tanto, es posible que sea propenso a errores.

Christoph
fuente
42
En realidad, debe verificar el tipo de argumento2 === "undefined", en caso de que alguien defina "undefined".
JW.
133
Añadiré un aviso, pero ¿qué bastardos enfermos hacen cosas así?
Christoph el
12
undefined es una variable en el espacio global. Es más lento buscar esa variable en el ámbito global que una variable en un ámbito local. Pero lo más rápido es usar typeof x === "undefined"
algunos
55
Interesante. Por otro lado, la comparación === undefined podría ser más rápida que la comparación de cadenas. Sin embargo, mis pruebas parecen indicar que tienes razón: x === indefinido necesario ~ 1.5x el tiempo de typeof x === 'undefined'
Christoph
44
@ Christoph: después de leer tu comentario, pregunté por ahí. Pude demostrar que la comparación de cadenas seguramente no es (solo) por comparación de puntero, ya que comparar una cadena gigantesca lleva más tiempo que una cadena pequeña. Sin embargo, comparar dos cadenas gigantescas es realmente lento solo si se construyeron con concatenación, pero realmente rápido si el segundo se creó como una asignación del primero. Entonces, probablemente funcione con una prueba de puntero seguida de una prueba letra por letra. Entonces, sí, estaba lleno de basura :) gracias por iluminarme ...
Juan Mendes
17

Si está usando jQuery, una opción que es buena (especialmente para situaciones complicadas) es usar el método extendido de jQuery .

function foo(options) {

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

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

Si llama a la función, entonces así:

foo({timeout : 500});

La variable de opciones sería entonces:

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

Este es uno de los pocos casos en los que encuentro la prueba:

if(! argument2) {  

}

funciona bastante bien y conlleva la implicación correcta sintácticamente.

(Con la restricción simultánea de que no permitiría un valor nulo legítimo para el argument2que tiene algún otro significado; pero eso sería realmente confuso).

EDITAR:

Este es un muy buen ejemplo de una diferencia estilística entre lenguajes poco tipados y fuertemente tipados; y una opción estilística que JavaScript ofrece en espadas.

Mi preferencia personal (sin críticas para otras preferencias) es el minimalismo. Cuanto menos tenga que decir el código, mientras sea coherente y conciso, menos tendrá que comprender alguien más para inferir correctamente mi significado.

Una implicación de esa preferencia es que no quiero, no me parece útil, acumular un montón de pruebas de dependencia de tipo. En cambio, trato de hacer que el código signifique lo que parece; y prueba solo para lo que realmente tendré que probar.

Una de las molestias que encuentro en el código de otras personas es saber si esperan o no, en un contexto más amplio, encontrarse con los casos que están probando. O si están tratando de probar todo lo posible, en la posibilidad de que no anticipen el contexto lo suficiente. Lo que significa que termino necesitando rastrearlos exhaustivamente en ambas direcciones antes de poder refactorizar o modificar cualquier cosa con confianza. Me imagino que hay una buena posibilidad de que hayan implementado esas diversas pruebas porque previeron circunstancias en las que serían necesarias (y que generalmente no son aparentes para mí).

(Considero que es una desventaja grave en la forma en que estas personas usan lenguajes dinámicos. Con demasiada frecuencia, las personas no quieren renunciar a todas las pruebas estáticas y terminan fingiendo).

He visto esto de manera más evidente al comparar el código completo de ActionScript 3 con el elegante código de JavaScript. El AS3 puede ser 3 o 4 veces el grueso del js, y la confiabilidad que sospecho no es al menos mejor, solo por la cantidad (3-4X) de decisiones de codificación que se tomaron.

Como dices, Shog9, YMMV. :RE

dkretz
fuente
1
if (! argumento2) argumento2 = 'predeterminado' es equivalente a argumento2 = argumento2 || 'default' - La segunda versión me parece visualmente más agradable ...
Christoph el
55
Y lo encuentro más detallado y distractor; pero es preferencia personal, estoy seguro.
dkretz
44
@le dorfier: también impide el uso de cadenas vacías, 0 y boolean false.
Shog9
@le dorfier: más allá de la estética, hay una diferencia clave: esta última crea efectivamente un segundo camino de ejecución, que podría tentar a los cuidadores descuidados a agregar un comportamiento más allá de la simple asignación de un valor predeterminado. YMMV, por supuesto.
Shog9
3
¿Qué pasa si el parámetro2 es un booleano === falso; o una función {return false;}?
Fresko
7

Hay diferencias significativas Configuremos algunos casos de prueba:

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

Con el primer método que describa, solo la segunda prueba usará el valor predeterminado. El segundo método por defecto todos excepto el primero (como JS convertirá undefined, null, 0, y ""en el booleano false. Y si usted fuera a utilizar el método de Tom, sólo la cuarta prueba usará el valor por defecto!

El método que elija realmente depende de su comportamiento previsto. Si undefinedse permiten valores distintos de argument2, probablemente querrá alguna variación en el primero; si se desea un valor no cero, no nulo, no vacío, entonces el segundo método es ideal; de hecho, a menudo se usa para eliminar rápidamente un rango tan amplio de valores de la consideración.

Shog9
fuente
7
url = url === undefined ? location.href : url;
Lamy
fuente
Los huesos desnudos responden. Alguna explicación no dolería.
Paul Rooney
Es un operador ternario que dice de manera concisa: si url no está definida (falta), configure la variable de url como location.href (la página web actual); de lo contrario, configure la variable de url como la url definida.
rmooney
5

En ES6 (ES2015) puede usar parámetros predeterminados

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

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

Andriy2
fuente
Esta respuesta es realmente interesante y podría ser útil para op. A pesar de que en realidad no responde a la pregunta
Ulysse BN
Como veo, él quiere definir arg si no se definió, así que
publiqué
Mi punto es que no está respondiendo a la mejor manera de determinar si un argumento no se envía a la función de JavaScript . Pero la pregunta podría responderse utilizando argumentos predeterminados: por ejemplo, nombrando argumentos con ellos "default value"y verificando si el valor es realmente "default value".
Ulysse BN
4

Lo siento, todavía no puedo comentar, así que para responder la respuesta de Tom ... En javascript (undefined! = Null) == false De hecho, esa función no funcionará con "null", debe usar "undefined"

Luca Matteis
fuente
1
Y Tom recibió dos votos a favor por una respuesta incorrecta: siempre es bueno saber qué tan buenos funcionan estos sistemas comunitarios;)
Christoph el
3

¿Por qué no usar el !!operador? Este operador, colocada antes de la variable, lo convierten a un valor lógico (si he entendido bien), por lo que !!undefinedy !!null(e incluso !!NaN, que puede ser muy interesante) devolverá false.

Aquí hay un ejemplo:

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

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

foo() //=> will log false
RallionRl
fuente
No funciona con una cadena booleana verdadera, cero y vacía. Por ejemplo, foo (0); se registrará falso, pero foo (1) se registrará verdadero
rosell.dk
2

Puede ser conveniente abordar la detección de argumentos evocando su función con un Objeto de propiedades opcionales:

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]; }
            }
        }
    }
}
1nfiniti
fuente
2

También hay una manera difícil de encontrar, si un parámetro se pasa a una función o no . Echa un vistazo al siguiente ejemplo:

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

Esto significa que si el valor de valueno está presente / pasado, configúrelo en 0.

Muy bien, ¿eh?

Mohammed Zameer
fuente
1
Esto realmente significa "si valuees equivalente a false, configúrelo en 0". Esta es una distinción sutil pero muy importante.
Charles Wood
@CharlesWood Valor no aprobado / presente significa que solo es falso
Mohammed Zameer
1
Claro, si eso se ajusta al requisito de su función. Pero si, por ejemplo, el parámetro es booleano, a continuación, truey falseson valores válidos, y es posible que desee tener un tercer comportamiento para cuando el parámetro no está aprobado en todos (especialmente si la función tiene más de un parámetro).
Charles Wood
1
Y debo reconocer que este es un gran argumento en ciencias de la computación y puede terminar siendo una cuestión de opinión: D
Charles Wood
@CharlesWood lo siento por llegar tarde a la fiesta. Le sugiero que agregue estos en la respuesta en sí con la opción de edición
Mohammed Zameer
1

Algunas veces también es posible que desee verificar el tipo, especialmente si está utilizando la función como getter y setter. El siguiente código es ES6 (no se ejecutará en EcmaScript 5 o anterior):

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;
            }
        }
    }
}
nbloqs
fuente
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 las matrices heredan de Object.prototype. Considere ⇑ para mejorar el mundo.

Elevado
fuente
-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

Si la función anterior se llama desde muchos lugares y está seguro de que los primeros 2 parámetros se pasan desde todas partes, pero no está seguro sobre el tercer parámetro, puede usar la ventana.

window.param3 se encargará si no está definido desde el método de la persona que llama.

Panda Goutam
fuente