Обработка необязательных параметров в javascript

127

У меня есть статическая функция javascript, которая может принимать 1, 2 или 3 параметра:

function getData(id, parameters, callback) //parameters (associative array) and callback (function) are optional

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

Как лучше всего это сделать?


Примеры того, что можно было передать:

1:

getData('offers');

2:

var array = new Array();
array['type']='lalal';
getData('offers',array);

3:

var foo = function (){...}
getData('offers',foo);

4:

getData('offers',array,foo);
юлианский день.
источник
2
Можете ли вы показать пример того, что можно передать?
Джеймс Блэк,

Ответы:

163

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

function getData (id, parameters, callback) {
  if (arguments.length == 2) { // if only two arguments were supplied
    if (Object.prototype.toString.call(parameters) == "[object Function]") {
      callback = parameters; 
    }
  }
  //...
}

Вы также можете использовать объект arguments таким образом:

function getData (/*id, parameters, callback*/) {
  var id = arguments[0], parameters, callback;

  if (arguments.length == 2) { // only two arguments supplied
    if (Object.prototype.toString.call(arguments[1]) == "[object Function]") {
      callback = arguments[1]; // if is a function, set as 'callback'
    } else {
      parameters = arguments[1]; // if not a function, set as 'parameters'
    }
  } else if (arguments.length == 3) { // three arguments supplied
      parameters = arguments[1];
      callback = arguments[2];
  }
  //...
}

Если вам интересно, прочтите эту статью Джона Ресига о технике имитации перегрузки методов в JavaScript.

CMS
источник
Зачем использовать Object.prototype.toString.call (parameters) == "[функция объекта]", а не typeof (parameters) === 'function'? Есть ли между ними какое-то важное различие? PS В статье, которую вы упомянули, похоже, используется последнее
Томер Каган
@TomerCagan Я думаю, это вопрос предпочтений (иш). Под этим вопросом есть несколько хороших ответов / комментариев по теме .
Philiiiiiipp
75

Эээ - это означало бы, что вы вызываете свою функцию с аргументами, которые не находятся в правильном порядке ... что я бы не рекомендовал.

Я бы рекомендовал вместо этого передать объект вашей функции следующим образом:

function getData( props ) {
    props = props || {};
    props.params = props.params || {};
    props.id = props.id || 1;
    props.callback = props.callback || function(){};
    alert( props.callback )
};

getData( {
    id: 3,
    callback: function(){ alert('hi'); }
} );

Преимущества:

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

Недостатки:

  • время рефакторинга кода

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

Примечание. Это правильный способ обнаружения функции:

function isFunction(obj) {
    return Object.prototype.toString.call(obj) === "[object Function]";
}

isFunction( function(){} )
Медер Омуралиев
источник
«это сработает в 99% случаев из-за некоторых ошибок ES». Вы можете объяснить больше? Почему все могло пойти не так?
jd.
Я добавил правильный код для обнаружения функции. Я считаю, что ошибка здесь: bugs.ecmascript.org/ticket/251
медер омуралиев
Я настоятельно рекомендую вам кормить только один объект. Если нет, используйте метод CMS.
медер омуралиев
Ох ... блин ... Только что выложил ту же идею.
Арнис Лапса,
1
Еще один возможный недостаток - отсутствие интеллекта. Я не считаю это большим делом, но следует отметить.
Edyn 03
2

Поэтому используйте оператор typeof, чтобы определить, является ли второй параметр массивом или функцией.

Это может дать несколько предложений: http://www.planetpdf.com/developer/article.asp?ContentID=testing_for_object_types_in_ja

Я не уверен, работа это или домашнее задание, поэтому я не хочу сейчас давать вам ответ, но тип поможет вам определить его.

Джеймс Блэк
источник
2

Вам следует проверить тип полученных параметров. Возможно, вам стоит использовать argumentsмассив, поскольку второй параметр может иногда быть «параметрами», а иногда «обратным вызовом», и наименование его параметров может вводить в заблуждение.

Камил Сот
источник
2

Я знаю, что это довольно старый вопрос, но я занимался этим недавно. Сообщите мне, что вы думаете об этом решении.

Я создал служебную программу, которая позволяет мне строго вводить аргументы и делать их необязательными. Вы в основном заключаете свою функцию в прокси. Если вы пропустите аргумент, он не определен . Это может показаться странным, если у вас есть несколько необязательных аргументов одного и того же типа рядом друг с другом. (Существуют варианты передачи функций вместо типов для проверки настраиваемых аргументов, а также указание значений по умолчанию для каждого параметра.)

Вот как выглядит реализация:

function displayOverlay(/*message, timeout, callback*/) {
  return arrangeArgs(arguments, String, Number, Function, 
    function(message, timeout, callback) {
      /* ... your code ... */
    });
};

Для наглядности вот что происходит:

function displayOverlay(/*message, timeout, callback*/) {
  //arrangeArgs is the proxy
  return arrangeArgs(
           //first pass in the original arguments
           arguments, 
           //then pass in the type for each argument
           String,  Number,  Function, 
           //lastly, pass in your function and the proxy will do the rest!
           function(message, timeout, callback) {

             //debug output of each argument to verify it's working
             console.log("message", message, "timeout", timeout, "callback", callback);

             /* ... your code ... */

           }
         );
};

Вы можете просмотреть код прокси-сервераrangeArgs в моем репозитории GitHub здесь:

https://github.com/joelvh/Sysmo.js/blob/master/sysmo.js

Вот служебная функция с некоторыми комментариями, скопированными из репозитория:

/*
 ****** Overview ******
 * 
 * Strongly type a function's arguments to allow for any arguments to be optional.
 * 
 * Other resources:
 * http://ejohn.org/blog/javascript-method-overloading/
 * 
 ****** Example implementation ******
 * 
 * //all args are optional... will display overlay with default settings
 * var displayOverlay = function() {
 *   return Sysmo.optionalArgs(arguments, 
 *            String, [Number, false, 0], Function, 
 *            function(message, timeout, callback) {
 *              var overlay = new Overlay(message);
 *              overlay.timeout = timeout;
 *              overlay.display({onDisplayed: callback});
 *            });
 * }
 * 
 ****** Example function call ******
 * 
 * //the window.alert() function is the callback, message and timeout are not defined.
 * displayOverlay(alert);
 * 
 * //displays the overlay after 500 miliseconds, then alerts... message is not defined.
 * displayOverlay(500, alert);
 * 
 ****** Setup ******
 * 
 * arguments = the original arguments to the function defined in your javascript API.
 * config = describe the argument type
 *  - Class - specify the type (e.g. String, Number, Function, Array) 
 *  - [Class/function, boolean, default] - pass an array where the first value is a class or a function...
 *                                         The "boolean" indicates if the first value should be treated as a function.
 *                                         The "default" is an optional default value to use instead of undefined.
 * 
 */
arrangeArgs: function (/* arguments, config1 [, config2] , callback */) {
  //config format: [String, false, ''], [Number, false, 0], [Function, false, function(){}]
  //config doesn't need a default value.
  //config can also be classes instead of an array if not required and no default value.

  var configs = Sysmo.makeArray(arguments),
      values = Sysmo.makeArray(configs.shift()),
      callback = configs.pop(),
      args = [],
      done = function() {
        //add the proper number of arguments before adding remaining values
        if (!args.length) {
          args = Array(configs.length);
        }
        //fire callback with args and remaining values concatenated
        return callback.apply(null, args.concat(values));
      };

  //if there are not values to process, just fire callback
  if (!values.length) {
    return done();
  }

  //loop through configs to create more easily readable objects
  for (var i = 0; i < configs.length; i++) {

    var config = configs[i];

    //make sure there's a value
    if (values.length) {

      //type or validator function
      var fn = config[0] || config,
          //if config[1] is true, use fn as validator, 
          //otherwise create a validator from a closure to preserve fn for later use
          validate = (config[1]) ? fn : function(value) {
            return value.constructor === fn;
          };

      //see if arg value matches config
      if (validate(values[0])) {
        args.push(values.shift());
        continue;
      }
    }

    //add a default value if there is no value in the original args
    //or if the type didn't match
    args.push(config[2]);
  }

  return done();
}
joelvh
источник
2

Я рекомендую вам использовать ArgueJS .

Вы можете просто ввести свою функцию следующим образом:

function getData(){
  arguments = __({id: String, parameters: [Object], callback: [Function]})

  // and now access your arguments by arguments.id,
  //          arguments.parameters and arguments.callback
}

На ваших примерах я считал, что вы хотите, чтобы ваш idпараметр был строкой, верно? Теперь getDataтребуется a String idи принимает варианты Object parametersи Function callback. Все опубликованные вами варианты использования будут работать должным образом.

zVictor
источник
1

Вы хотите сказать, что у вас могут быть такие вызовы: getData (id, parameters); getData (идентификатор, обратный вызов)?

В этом случае вы, очевидно, не можете полагаться на позицию, и вам нужно полагаться на анализ типа: getType (), а затем, при необходимости, getTypeName ()

Проверьте, является ли рассматриваемый параметр массивом или функцией.

DmitryK
источник
0

Я думаю, вы хотите использовать здесь typeof ():

function f(id, parameters, callback) {
  console.log(typeof(parameters)+" "+typeof(callback));
}

f("hi", {"a":"boo"}, f); //prints "object function"
f("hi", f, {"a":"boo"}); //prints "function object"
Марк Бесси
источник
0

Если ваша проблема связана только с перегрузкой функции (вам нужно проверить, является ли параметр «параметры» «параметрами», а не «обратным вызовом»), я бы рекомендовал вам не беспокоиться о типе аргумента и
использовать этот подход . Идея проста - используйте буквальные объекты для объединения ваших параметров:

function getData(id, opt){
    var data = voodooMagic(id, opt.parameters);
    if (opt.callback!=undefined)
      opt.callback.call(data);
    return data;         
}

getData(5, {parameters: "1,2,3", callback: 
    function(){for (i=0;i<=1;i--)alert("FAIL!");}
});
Арнис Лапса
источник
0

Я думаю, это может быть очевидным примером:

function clickOn(elem /*bubble, cancelable*/) {
    var bubble =     (arguments.length > 1)  ? arguments[1] : true;
    var cancelable = (arguments.length == 3) ? arguments[2] : true;

    var cle = document.createEvent("MouseEvent");
    cle.initEvent("click", bubble, cancelable);
    elem.dispatchEvent(cle);
}
сес
источник
-5

Можете ли вы переопределить функцию? Это не сработает:

function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}
J.Hendrix
источник
8
Нет, это не сработает. Вы не получите никаких ошибок, но Javascript всегда будет использовать последнюю функцию, которую вы определили.
jd.
6
Вот это да. Я думал, ты сошел с ума. Я только что это проверил. Ты прав. Мой мир только что немного изменился для меня. Я думаю, что сегодня мне нужно изучить много JavaScript, чтобы убедиться, что у меня нет ничего подобного в производстве. Спасибо за комментарий. Я поставил вам +1.
J.Hendrix, 07