Многие сталкивались с необходимостью использовать необязательные аргументы функции. Если такой аргумент один, да ещё и последний, то особых проблем не возникает.
Задача усложняется, если два последних аргумента могут отсутствовать. Естественно они должны быть разного типа. Типичный случай, когда последний аргумент это колбек, а предпоследний некая опция или набор опций. Например, метод send посылает некие данные некоему адресату. Необходимо ему передать только сами данные, но возможно, уточнить способ передачи с помощью набора опций и передать колбек, который вызовется после завершения запроса.
Теперь усложняем еще. Адресат уже не некий, его надо уточнять с помощью параметра id и вместе с этим появляется желание отправить пачку запросов разным адресатам. Что делать? Логика функции заточена под наш, почти детерминированный, набор аргументов, поэтому, особо не вдаваясь в подробности (т.к. мы уже забыли что там понаписано) оборачиваем её (логику) во внутреннюю функцию и кладём вызов в цикл. Теперь внутренняя функция принимает первоначальный набор аргументов,
а с набором внешней опять надо хитрить.
pack это двумерный массив.
Казалось бы, всё, хитрости закончились, но жизнь подбрасывает ещё один аргумент, причём общий для всей пачки. Примерно такой сценарий развития событий подвёл меня к осознанию необходимости более простого и красивого способа разбора аргументов. Недолгие раздумья привели меня к использованию Си-подобных прототипов. Идея такая. Делаем класс, конструктор которого принимает arguments, а его метод checkin проверяет соответствие аргументов тому или иному прототипу.
Чтобы стало понятно, как это работает, для начала привожу пример использования. Новый аргумент назовём, не мудрствуя, newArg.
И если жизнь подбросит нам ещё один необязательный аргумент, то можно будет его пристроить без опаски сломать логику разбора аргументов.
Теперь сам код:
Примеры в тексте носят исключительно поясняющий характер и не проверялись на работоспособность. Зато здесь можно посмотреть работающий пример.
function set(name, value){
if(value == undefined){
value = name;
}
...
}
Задача усложняется, если два последних аргумента могут отсутствовать. Естественно они должны быть разного типа. Типичный случай, когда последний аргумент это колбек, а предпоследний некая опция или набор опций. Например, метод send посылает некие данные некоему адресату. Необходимо ему передать только сами данные, но возможно, уточнить способ передачи с помощью набора опций и передать колбек, который вызовется после завершения запроса.
function send(data){
if(arguments[1] instanceof Function){
var callback = arguments[1];
var options = null;
}
else if(arguments[2] instanceof Function){
var callback = arguments[2];
var options = arguments[1];
}
...
}
Теперь усложняем еще. Адресат уже не некий, его надо уточнять с помощью параметра id и вместе с этим появляется желание отправить пачку запросов разным адресатам. Что делать? Логика функции заточена под наш, почти детерминированный, набор аргументов, поэтому, особо не вдаваясь в подробности (т.к. мы уже забыли что там понаписано) оборачиваем её (логику) во внутреннюю функцию и кладём вызов в цикл. Теперь внутренняя функция принимает первоначальный набор аргументов,
а с набором внешней опять надо хитрить.
function send(){
function __send(id, data){
...
}
if(arguments[0] instanceof Array){
var pack = arguments[0];
var callback = arguments[1];
for(var i=0; i<pack.length; i++){
__send.apply(this, pack[i]);
}
}
}
pack это двумерный массив.
Казалось бы, всё, хитрости закончились, но жизнь подбрасывает ещё один аргумент, причём общий для всей пачки. Примерно такой сценарий развития событий подвёл меня к осознанию необходимости более простого и красивого способа разбора аргументов. Недолгие раздумья привели меня к использованию Си-подобных прототипов. Идея такая. Делаем класс, конструктор которого принимает arguments, а его метод checkin проверяет соответствие аргументов тому или иному прототипу.
Чтобы стало понятно, как это работает, для начала привожу пример использования. Новый аргумент назовём, не мудрствуя, newArg.
function send(){
function __send(){
var args = new argSchema(arguments);
if(args.checkin('number id', 'object data', 'opt bool newArg', 'opt function callback')){
//Используем параметры args.id, args.data, args.newArg, args.callback
...
}
...
}
var args = new argSchema(arguments);
if(args.checkin('array pack', 'opt bool newArg', 'opt function callback')){
for(var i=0; i<pack.length; i++){
__send.apply(this, args.pack[i]);
}
}
else if(args.checkin('number id', 'object data', 'opt bool newArg', 'opt function callback')){
__send(args.id, args.data, args.newArg, args.callback);
}
}
И если жизнь подбросит нам ещё один необязательный аргумент, то можно будет его пристроить без опаски сломать логику разбора аргументов.
Теперь сам код:
var is = function( type, obj ) {
return Object.prototype.toString.call( obj ) === "[object "+ type +"]";
}
is['string'] = function(obj){
return (typeof obj == 'string' || obj instanceof String);
}
is.number = function(obj){
return (typeof obj == 'number' || obj instanceof Number);
}
is.bool = function(obj){
return (typeof obj == 'boolean' || obj instanceof Boolean);
}
is.object = function(obj){
return (typeof obj == 'object');
}
is.array = function(obj){
return (obj instanceof Array);
}
is.func = function(obj){
return (obj instanceof Function);
}
is.set = function(obj){
return (obj != undefined && obj != null);
}
is.unset = function(obj){
return !this.set(obj);
}
function argSchema(argArr){
this.checkin = function(){
var arr, qual, type, name;
function check(type, arg){
if(is.unset(arg)) return true;
switch(type){
case 'string':
if(is.string(arg)){
this[name] = arg;
}
else return false;
break;
case 'number':
if(is.number(arg)){
this[name] = arg;
}
else return false;
break;
case 'bool':
if(is.bool(arg)){
this[name] = arg;
}
else return false;
break;
case 'object':
if(is.object(arg)){
this[name] = arg;
}
else return false;
break;
case 'function':
if(is.func(arg)){
this[name] = arg;
}
else return false;
break;
case 'array':
if(is.array(arg)){
this[name] = arg;
}
else return false;
break;
}
return true;
}
for(var i=0, j=0; i<arguments.length; i++){
arr = arguments[i].split(' ');
qual = type = name = null;
if(arr.length == 3){
qual = arr[0];
type = arr[1];
name = arr[2];
}
else if(arr.length == 2){
type = arr[0];
name = arr[1];
}
delete this[name];
if(qual == 'opt'){
if(check.call(this, type, argArr[j])){
j++;
}
}
else{
if(!check.call(this, type, argArr[j])) return false;
j++;
}
}
return true;
}
}
Примеры в тексте носят исключительно поясняющий характер и не проверялись на работоспособность. Зато здесь можно посмотреть работающий пример.