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

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

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

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

Какой лучший способ сделать это?


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

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);

Ответ 1

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

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.

Ответ 2

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

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

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(){} )

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

Это выглядит так:

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 working
             console.log("message", message, "timeout", timeout, "callback", callback);

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

           }
         );
};

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

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

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

/*
 ****** Overview ******
 * 
 * Strongly type a function 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 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();
}

Ответ 7

Вы говорите, что у вас могут быть такие звонки: getData (id, параметры); getData (id, callback)?

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

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

Ответ 8

Я рекомендую вам использовать 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 требуется String id и принимает опции Object parameters и Function callback. Все используемые вами варианты использования будут работать как ожидалось.

Ответ 9

Я думаю, вы хотите использовать 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"

Ответ 10

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

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!");}
});

Ответ 11

Я думаю, это может быть объяснительный пример:

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);
}

Ответ 12

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

function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}