Схема аргументов javascript функции или C-style прототипы без тяжёловесных фреймворков

от автора

Многие сталкивались с необходимостью использовать необязательные аргументы функции. Если такой аргумент один, да ещё и последний, то особых проблем не возникает.

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

Примеры в тексте носят исключительно поясняющий характер и не проверялись на работоспособность. Зато здесь можно посмотреть работающий пример.

ссылка на оригинал статьи http://habrahabr.ru/post/195816/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *