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