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 */ ); }; })();