Как лучше всего определить, не передан ли аргумент в функцию JavaScript

236

Теперь я видел 2 метода для определения, был ли аргумент передан функции JavaScript. Мне интересно, если один метод лучше, чем другой, или один просто плохо использовать?

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

      alert(argument2);
 }

 Test('test');

Или

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

      alert(argument2);
 }

 Test('test');

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

Другой вариант, упомянутый Томом :

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

    alert(argument2);
}

Согласно комментарию Хуана, было бы лучше изменить предложение Тома на:

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

    alert(argument2);
}
Дэррил Хейн
источник
Это действительно то же самое. Если у вас всегда есть статическое число аргументов, используйте второй метод, в противном случае, вероятно, будет проще выполнить итерацию с использованием массива arguments.
Лука Маттеис
7
Аргумент, который не был передан, считается неопределенным. Тестирование со строгим равенством против нуля не удастся. Вы должны использовать строгое равенство с неопределенным.
Хуан Мендес
19
Помните: argument2 || 'blah';приведет к «бла», если argument2есть false(!), А не просто, если оно не определено. Если argument2это логическое значение, и falseдля него передается функция , эта строка вернет «бла», несмотря на argument2то, что она определена правильно .
Сэнди Гиффорд
9
@SandyGifford: Та же проблема , если argument2есть 0, ''или null.
rvighne
@rvighne Очень верно. Уникальная интерпретация объектов и кастинга в Javascript - это одновременно и лучшие, и худшие стороны.
Сэнди Гиффорд

Ответы:

274

Есть несколько разных способов проверить, был ли передан аргумент функции. В дополнение к двум из них, которые вы упомянули в своем (оригинальном) вопросе - проверке arguments.lengthили использовании ||оператора для предоставления значений по умолчанию - можно также явно проверить аргументы для undefinedvia argument2 === undefinedили typeof argument2 === 'undefined'если вы параноик (см. Комментарии).

Использование ||оператора стало стандартной практикой - все крутые дети делают это - но будьте осторожны: Значение по умолчанию будет срабатывать , если аргумент имеет значение false, что означает , что она может быть на самом деле undefined, null, false, 0, ''(или что - нибудь еще , для которого Boolean(...)возвращается false).

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

Проверка arguments.lengthдемонстрирует «наиболее правильное» поведение, но это может оказаться невозможным, если имеется более одного необязательного аргумента.

Следующий критерий undefinedявляется «лучшим» - он только «проваливается», если функция явно вызывается со undefinedзначением, которое, по всей вероятности, следует обрабатывать так же, как и пропустить аргумент.

Использование ||оператора может инициировать использование значения по умолчанию, даже если указан допустимый аргумент. С другой стороны, его поведение действительно может быть желательным.

Подводя итог: используйте его, только если знаете, что делаете!

На мой взгляд, использование ||- это также путь, если есть более одного необязательного аргумента, и вы не хотите передавать литерал объекта в качестве обходного пути для именованных параметров.

Другой хороший способ обеспечить использование значений по умолчанию arguments.lengthвозможен, если пролистать метки оператора 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
}

Это имеет обратную сторону: намерение программиста не является (визуально) очевидным и использует «магические числа»; поэтому возможно ошибки.

Christoph
источник
42
Вы должны на самом деле проверить typeof аргумент2 === "undefined", на случай, если кто-то определит "undefined".
JW.
133
Я добавлю уведомление - но какие больные ублюдки делают такие вещи?
Кристоф
12
undefined - это переменная в глобальном пространстве. Поиск этой переменной в глобальной области медленнее, чем в локальной области. Но самое быстрое - это использовать typeof x === "undefined"
примерно
5
Интересный. С другой стороны, сравнение === undefined может быть быстрее, чем сравнение строк. Похоже, мои тесты показывают, что вы правы: x === undefined требуется ~ 1.5x время typeof x === 'undefined'
Кристоф
4
@Cristoph: после прочтения вашего комментария я спросил вокруг. Мне удалось доказать, что сравнение строк, безусловно, не (только) путем сравнения указателей, поскольку сравнение гигантской строки занимает больше времени, чем маленькая строка. Однако сравнение двух гигантских строк действительно медленное, только если они были созданы с конкатенацией, но очень быстрое, если вторая была создана как назначение из первой. Так что это, вероятно, работает тестом указателя, за которым следует буквенно-буквенное тестирование. Так что, да, я был полон дерьма :) спасибо за то, что просветили меня ...
Хуан Мендес
17

Если вы используете jQuery, один вариант, который хорош (особенно для сложных ситуаций), - это использовать метод extends jQuery .

function foo(options) {

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

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

Если вы вызываете функцию, то вот так:

foo({timeout : 500});

Переменная options тогда будет:

{
    timeout : 500,
    callback : function(){},
    some_number : 50,
    some_text : "hello world"
};
Грег Татум
источник
15

Это один из немногих случаев, когда я нахожу тест:

if(! argument2) {  

}

работает довольно хорошо и несет правильное значение синтаксически.

(С одновременным ограничением, которое я не позволил бы допустимому нулевому значению, для argument2которого есть некоторое другое значение; но это было бы действительно запутанным.)

РЕДАКТИРОВАТЬ:

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

Мое личное предпочтение (без критики для других предпочтений) - минимализм. Чем меньше должен сказать код, пока я последовательный и лаконичный, тем меньше кто-то другой должен понять, чтобы правильно понять мой смысл.

Одним из следствий этого предпочтения является то, что я не хочу - не считаю это полезным - накапливать кучу тестов на зависимости типов. Вместо этого я пытаюсь сделать так, чтобы код имел в виду то, как он выглядит; и проверять только то, что мне действительно нужно проверить.

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

(Я считаю серьезным недостатком то, как эти люди используют динамические языки. Слишком часто люди не хотят отказываться от всех статических тестов и заканчивают тем, что имитируют его.)

Я видел это наиболее ярко при сравнении всеобъемлющего кода ActionScript 3 с элегантным кодом javascript. AS3 может в 3 или 4 раза превышать объем js, и я подозреваю, что надежность, по крайней мере, не лучше, просто из-за количества (3-4X) решений по кодированию, которые были приняты.

Как вы говорите, Shog9, YMMV. : D

dkretz
источник
1
if (! arguments2) аргумент2 = 'по умолчанию' эквивалентен аргументу2 = аргумент2 || 'default' - вторая версия визуально более приятна ...
Кристоф,
5
И я нахожу это более многословным и отвлекающим; но это личное предпочтение, я уверен.
dkretz
4
@le dorfier: это также исключает использование пустых строк, 0 и логического false.
Shog9
@le dorfier: помимо эстетики, есть одно ключевое отличие: последнее эффективно создает второй путь выполнения, который может соблазнить неосторожных сопровождающих добавить поведение помимо простого присвоения значения по умолчанию. YMMV, конечно.
Shog9
3
что если параметр2 является логическим значением === false; или функция {return false;}?
Фреска
7

Есть существенные различия. Давайте настроим несколько тестовых случаев:

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

При первом описанном вами методе только второй тест будет использовать значение по умолчанию. Второй метод будет по умолчанию все , кроме первого (как JS будет конвертировать undefined, null, 0и ""в логическое значение false. И если бы вы использовали метод Тома, только четвертый тест будет использовать по умолчанию!

Какой метод вы выберете, действительно зависит от вашего предполагаемого поведения. Если значения, отличные от undefinedдопустимых argument2, то вам, вероятно, понадобится некоторое изменение первого; если желательно ненулевое, ненулевое, непустое значение, тогда второй метод идеален - действительно, он часто используется для быстрого исключения из рассмотрения такого широкого диапазона значений.

Shog9
источник
7
url = url === undefined ? location.href : url;
Лами
источник
Голые кости отвечают. Некоторое объяснение не повредит.
Пол Руни
Это троичный оператор, который кратко говорит: если url не определен (отсутствует), установите переменную url равной location.href (текущая веб-страница), в противном случае установите переменную url равной определенному URL.
rmooney
5

В ES6 (ES2015) вы можете использовать параметры по умолчанию

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

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

Andriy2
источник
Этот ответ действительно интересен и может быть полезен для оп. Хотя это действительно не отвечает на вопрос
Ulysse BN
Как я вижу - он хочет определить arg, если он не был определен, поэтому я разместил некоторую полезную информацию
Andriy2
Суть в том, что вы не отвечаете на вопрос « Как лучше определить, не отправлен ли аргумент в функцию JavaScript» ? Но на этот вопрос можно ответить, используя аргументы по умолчанию: например, присвоив имена аргументам "default value"и проверив, действительно ли это значение "default value".
Ulysse BN
4

Извините, я все еще не могу комментировать, поэтому, чтобы ответить на вопрос Тома ... В javascript (undefined! = Null) == false На самом деле эта функция не будет работать с "null", вы должны использовать "undefined"

Лука Маттеис
источник
1
И Том получил два отзыва за неправильный ответ - всегда приятно знать, насколько хорошо работают эти системы сообщества;)
Кристоф
3

Почему бы не использовать !!оператор? Этот оператор, помещенный перед переменной, превращает ее в логическое значение (если я хорошо понял), поэтому !!undefinedи !!null(и даже !!NaN, что может быть довольно интересно) вернетfalse .

Вот пример:

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

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

foo() //=> will log false
RallionRl
источник
Не работает с логическим значением true, нулевой и пустой строкой. Например, foo (0); будет записывать ложь, но foo (1) будет записывать истину
rosell.dk
2

Может быть удобно приблизиться к обнаружению аргумента, вызывая вашу функцию с Объектом дополнительных свойств:

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
источник
2

Есть также хитрый способ выяснить, передан ли параметр в функцию или нет . Посмотрите на приведенный ниже пример:

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

Это означает, что если значение valueне указано / не передано, установите значение 0.

Довольно круто, да!

Мохаммед Замир
источник
1
Это на самом деле означает «если valueэквивалентно false, установить его на 0.» Это тонкое, но очень важное различие.
Чарльз Вуд
@CharlesWood Значение не передано / настоящее означает только ложь
Мохаммед Замир
1
Конечно, если это соответствует требованию вашей функции. Но если, например, ваш параметр имеет логическое значение, то trueи falseявляются допустимыми значениями, и вы можете захотеть иметь третье поведение, когда параметр вообще не передается (особенно если функция имеет более одного параметра).
Чарльз Вуд
1
И я должен признать, что это огромный аргумент в информатике и может в конечном итоге стать вопросом мнения: D
Чарльз Вуд
@CharlesWood, извините за опоздание на вечеринку. Я предлагаю вам добавить их в самом ответе с опцией редактирования
Мохаммед Замир
1

Иногда вам также может понадобиться проверить тип, особенно если вы используете функцию как метод получения и установки. Следующий код ES6 (не будет работать в EcmaScript 5 или более ранней версии):

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
источник
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`);
  }
}

Потому что массивы наследуются от Object.prototype. Подумайте, чтобы сделать мир лучше.

поднятый
источник
-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

Если указанная выше функция вызывается из многих мест, и вы уверены, что первые 2 параметра передаются отовсюду, но не уверены в третьем параметре, тогда вы можете использовать окно.

window.param3 будет обрабатывать, если он не определен из метода вызывающего.

Гутам Панда
источник