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

в 6:09, , рубрики: call, javascript, аргументы, Си, типизация, метки: , , , , ,

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

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

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

Автор: if0

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js