K345.is() source

source code
/**
* test a value against one or several certain type(s).
*
* K345.is() tries to be strict, e.g. testing "null" in mode 'object' would
* return "false", although "null" is of type object.
*
* @param {Mixed} item
*     value to test
* @param {string} cmp
*     type string to test "item" against
* @returns {Boolean}
*     "true" if "item" type matches one of "cmp"
* @foo
*
* some possible type strings for "cmp" are:
*     'string', 'number', 'object', 'null', 'nan', 'infinity', 'undefined', 'boolean',
*     'array', 'node', 'text', 'list', 'date', 'regexp', 'function', 'math' etc.
*
* @example
* K345.is(4, 'number'); // true
* K345.is(true, 'boolean'); // true
* K345.is({}, 'object'); // true
* K345.is([], 'object'); // false
* K345.is(document.getElementById(foo), 'node');
*
*
* K345.is() can also test if "item" matches one of several types:
*
* if (K345.is(c, 'number', 'string', 'boolean')) {...}
*
* is similar to
*
* if (typeof c === 'number' || typeof c === 'string' || typeof c === 'boolean') {...}
*
* @function
* @namespace
* @reqires String.prototype.contains  (in file)
* @reqires Array.prototype.map
* @reqires Array.prototype.some
* @reqires Array.prototype.every
*/
K345.is = (function () {
	/* shortcuts */
	var opToStr = Object.prototype.toString,
		apSlice = Array.prototype.slice,

		/* RegExp for type-of-object test */
		reg_object = (/object\s+([a-z]+)/i),

		/* method test */
		str_method = 'function|object',

		/* names of possible object version of primitives */
		str_prim = 'string|boolean|number',

		/* valid values for nodelist or htmlcollection property 'item' */
		str_listprop = str_method + '|string', /* string is for IE7 */

		/* some names of cmpmode strings which can be exactly the same as the
		* return value of getType() of an object */
		str_obj = 'array|regexp|date|math';

	/**
	* extract lowercase object type of toString() call;
	* e.g. [object Array] returns array
	*/
	function getType(item) {
		return opToStr.call(item)
			.match(reg_object)[1]
			.toLowerCase();
	}

	/** test: obj.nodeType exists and has a value of nType */
	function testNodeType(obj, nType) {
		return (
			'nodeType' in obj &&
			obj.nodeType === nType
		);
	}

	/** test: obj is a node Element*/
	function testNode(obj, objStr) {
		return (
			objStr.includes('html') && objStr.includes('element', 4)
		) || (
			/* duck typing for crappy browsers */
			testNodeType(obj, 1) &&
			str_method.includes(typeof obj.cloneNode)
		);
	}

	/** test: obj is a text node */
	function testTextNode(obj) {
		return (
			testNodeType(obj, 3) &&
			'nodeName' in obj &&
			obj.nodeName === '#text'
		);
	}

	/** test: obj is a live DOM list (NodeList, HTMLCollection) */
	function testList(obj, objStr) {
		return (
			objStr.includes('nodelist') || objStr.includes('htmlcollection')
		) || (
			/* duck typing for crappy browsers */
			objStr === 'object' &&
			'item' in obj &&
			typeof obj.length === 'number' &&
			str_listprop.includes(typeof obj.item)
		);
	}

	/** test: item is a node, a list, a textnode or if param equals objString  */
	function testType(item, param, objStr) {
		var rv;

		switch (param) {
		case 'node':
			rv = testNode(item, objStr);
			break;
		case 'text':
			rv = testTextNode(item);
			break;
		case 'list':
			rv = testList(item, objStr);
			break;
		default:
			rv = (param === objStr);
			break;
		}
		return rv;
	}

	/**
	* ~~~ callback function for .every() ~~~
	* returns true if type of param is a non empty string
	*/
	function cb_testParam(param) {
		return (typeof param === 'string' && param !== '');
	}

	/**
	* ~~~ callback function for .map() ~~~
	* returns lowercase string of param
	*/
	function cb_lower(param) {
		return param.toLowerCase();
	}

	/**
	* ~~~ callback function for .some() ~~~
	*
	* test type of item
	*
	* param is the cmp parameter of the current iteration
	*/
	function cb_typeTest(param) {
		var rv = false,
			item = this.item,
			itemType = typeof item,
			objStr, undef;

		/* immediately return if item is null, saves a few comparsions later on */
		if (item === null) {
			return (param === 'null');
		}

		switch (itemType) { /* switch on type of first parameter of is() call */

		case 'number':
			if (item === Infinity) {
				rv = (param === 'infinity');
			}
			else if (isNaN(item)) {
				rv = (param === 'nan');
			}
			else {
				if (param === 'int') {
					rv = (item === Math.floor(item));
				}
				else if (param === 'float') {
					rv = (item !== Math.floor(item));
				}
				else {
					rv = (param === 'number');
				}
			}
			break;

		/* typeof is broken and returns "object" for (way too) many types */
		case 'object':
			objStr = getType(item);

			/* test for some objects where object type matches parameter */
			if (str_obj.includes(objStr)) {
				rv = (param === objStr);
			}
			/* objects created with new String, new Number, new Boolean */
			else if (str_prim.includes(objStr)) {
				rv = (param === 'object');
			}
			else {
				/* custom objects */
				if (param === 'object' && objStr === 'object') {
					rv = (
						(item instanceof Object || item.prototype === undef) &&
						!testNode(item, objStr) &&
						!testTextNode(item) &&
						!testList(item, objStr)
					);
				}
				else {
					rv = testType(item, param, objStr);
				}
			}
			break;

		case 'function':
			objStr = getType(item);

			rv = (objStr !== 'function')
				? testType(item, param, objStr)
				: (param === 'function');
			break;

		default:
			rv = (param === itemType);
			break;
		}

		return rv;
	}

	return function (item /* , cmptype, cmptype, ... */) {
		var cmp;

		/* convert parameters to array */
		cmp = apSlice.call(arguments, 1);

		/* if "this" is an array, concatenate parameter array "cmp" with "this" */
		if (getType(this) === 'array' && this.length > 0) {
			cmp = cmp.concat(this);
		}

		/* item plus at least one "cmptype" parameter is required */
		if (cmp.length < 1) {
			throw new Error('K345.is(): at least two parameters required.');
		}

		/* test if all parameters are of type "string" */
		if (!cmp.every(cb_testParam)) {
			throw new Error('K345.is(): at least one comparsion type parameter is' +
				'empty or not a string.');
		}

		/*
		* loop through "cmptype" parameters
		* returns true if any of the type descriptions from the "cmptype" parameters
		* match the type of parameter "item"
		*/
		return cmp.map(
			cb_lower
		).some(
			cb_typeTest,
			{item: item} /* pass data to callback */
		);
	};
})();