javascript学习-类型判断
1.类型判断的的武器
javascript中用于类型判断的武器基本上有以下几种:
- 严格相等===,用来判断null,undefined,true,false这种有限值的数据类型很好用,唯一需要注意的是NaN !== NaN
- typeof运算符,用来判断其余的基本类型很好用
- Object.prototype.toString,用来判断对象类型很好用
2.类型判断的实现
了解了我们手中的武器,代码是非常简单了。当然了,这些函数都是自己测试时用的,不能直接用于商业库中,毕竟很多兼容性等细节问题没有考虑:
// isType function function _isNumber(value) { return typeof value === "number"; } function _isString(value) { return typeof value === "string"; } function _isBoolean(value) { return typeof value === "boolean"; } function _isUndefined(value) { // return typeof value === "undefined"; return value === (void 0); } function _isNull(value) { return value === null; } function _isPrimitive(value) { return _isNumber(value) || _isString(value) || _isBoolean(value) || _isNull(value) || _isUndefined(value); } function _isZero(value) { return value === 0; } // Is the given value `NaN`? (NaN is the only number which does not equal itself). function _isNaN(value) { return _isNumber(value) && value !== value; } function _isInfinity(value) { return value === Infinity || value === -Infinity; } function _isFinity(value) { return _isNumber(value) && !_isInfinity(value) && !_isNaN(value); } function _isObject(value) { return !_isPrimitive(value); } function _isArray(value) { // return Array.isArray(value); return Object.prototype.toString.call(value) === '[object Array]'; } function _isFunction(value) { return Object.prototype.toString.call(value) === '[object Function]'; } function _isRegExp(value) { return Object.prototype.toString.call(value) === '[object RegExp]'; } function _isDate(value) { return Object.prototype.toString.call(value) === '[object Date]'; } function _isError(value) { return Object.prototype.toString.call(value) === '[object Error]'; } function _isNumberObj(value) { return Object.prototype.toString.call(value) === '[object Number]' && !_isNumber(value); } function _isStringObj(value) { return Object.prototype.toString.call(value) === '[object String]' && !_isString(value); } function _isBooleanObj(value) { return Object.prototype.toString.call(value) === '[object Boolean]' && !_isBoolean(value); } function _isArguments(value) { return Object.prototype.toString.call(value) === '[object Arguments]'; }
3.类型判断的测试
基于我们的学习传统,一段测试用例那是必须的。不过首先我们需要准备测试数据:
var caseDatas = [ // number { sample : NaN }, { sample : 0 }, { sample : +0 }, { sample : -0 }, { sample : 1 }, { sample : 012 }, { sample : 0x12 }, { sample : 1.2 }, { sample : 1.217e2 }, { sample : Infinity }, { sample : -1 }, { sample : -012 }, { sample : -0x12 }, { sample : -1.2 }, { sample : -1.217e2 }, { sample : -Infinity }, // string { sample : "" }, { sample : "abc" }, { sample : "1abc" }, { sample : "NaN" }, { sample : "0" }, { sample : "+0" }, { sample : "-0" }, { sample : "1" }, { sample : "012" }, { sample : "0x12" }, { sample : "1.2" }, { sample : "1.217e2" }, { sample : "Infinity" }, { sample : "-1" }, { sample : "-012" }, { sample : "-0x12" }, { sample : "-1.2" }, { sample : "-1.217e2" }, { sample : "-Infinity" }, // boolean { sample : true }, { sample : false }, // remain primitive { sample : null }, { sample : undefined }, // built-in function { sample : new Object() }, { sample : new Array() }, { sample : new Function() }, { sample : new RegExp() }, { sample : new Date() }, { sample : new Error() }, { sample : new Number() }, { sample : new String() }, { sample : new Boolean() }, // built-in literals { sample : [] }, { sample : [1] }, { sample : [1,2,3] }, { sample : {} }, { sample : {name : "wgs", age : 18} }, { sample : function() {} }, { sample : function(a, b) { return a + b; } }, ];
基本上把我们可能想到的数据都加上去了,当然你也可以自己继续添加。
至于为什么用一个对象的形式对测试数据进行了一次包装?主要是为了兼容一般的测试用例数据,我们现在的测试用例不需要检测测试结果,如果需要的话,那么测试用例数据一般是:
{ sample : NaN, result : false }
下面来写测试函数:
function testCase(caseData, fn) { // 得到测试数据的采样值 var sample = caseData.sample; // 得到判断函数的结果 var isOk = fn(sample); if (isOk) { // 如果判断是这种类型,用_varDesc函数得到值的描述 var desc = _varDesc(sample); // 添加到测试节点上,这里当然都是true的 assert(isOk, desc); } }
下面写测试代码,对我们的类型判断函数逐个进行测试:
test("_isNumber", function() { caseDatas.forEach(function(value) { testCase(value, _isNumber); }); }); test("_isString", function() { caseDatas.forEach(function(value) { testCase(value, _isString); }); }); test("_isBoolean", function() { caseDatas.forEach(function(value) { testCase(value, _isBoolean); }); }); test("_isUndefined", function() { caseDatas.forEach(function(value) { testCase(value, _isUndefined); }); }); test("_isNull", function() { caseDatas.forEach(function(value) { testCase(value, _isNull); }); }); test("_isZero", function() { caseDatas.forEach(function(value) { testCase(value, _isZero); }); }); test("_isNaN", function() { caseDatas.forEach(function(value) { testCase(value, _isNaN); }); }); test("_isInfinity", function() { caseDatas.forEach(function(value) { testCase(value, _isInfinity); }); }); test("_isFinity", function() { caseDatas.forEach(function(value) { testCase(value, _isFinity); }); }); test("_isObject", function() { caseDatas.forEach(function(value) { testCase(value, _isObject); }); }); test("_isArray", function() { caseDatas.forEach(function(value) { testCase(value, _isArray); }); }); test("_isFunction", function() { caseDatas.forEach(function(value) { testCase(value, _isFunction); }); }); test("_isRegExp", function() { caseDatas.forEach(function(value) { testCase(value, _isRegExp); }); }); test("_isDate", function() { caseDatas.forEach(function(value) { testCase(value, _isDate); }); }); test("_isError", function() { caseDatas.forEach(function(value) { testCase(value, _isError); }); }); test("_isNumberObj", function() { caseDatas.forEach(function(value) { testCase(value, _isNumberObj); }); }); test("_isStringObj", function() { caseDatas.forEach(function(value) { testCase(value, _isStringObj); }); }); test("_isBooleanObj", function() { caseDatas.forEach(function(value) { testCase(value, _isBooleanObj); }); });
结果是html的,但是我不知道怎么插入博客中,只好最笨的方式来截图了:
结果当然是测试通过了,如果不通过,我早就悄悄的改了,呵呵。
4.测试数据的显示
本来javascript中如何显示一个值JSON.stringify之类的函数已经考虑到了。但是这玩意毕竟是给机器看的,而不是给人看的,我不满意的主要是:
- 对undefined返回undefined,而不是一个字符串,想想也能忍受
- 对函数返回undefined,你给我个函数名不是更好吗?
- 对NaN,Infinity直接返回null,这个不能忍了
既然你不提供,那我就自己来吧,所以就有了下面的函数_varDesc,之所以用_开头是为了避免名称冲突,毕竟这个小玩意主要是为了自己用着方便,直接放在全局对象中了,就没必要弄个即时函数啥的吧。
function _varDesc(value) { var ret = ""; if (_isNumber(value)) { ret = "number({0})".fmt(value); } else if (_isString(value)) { ret = "string("{0}")".fmt(value); } else if (_isBoolean(value)) { ret = "boolean({0})".fmt(value); } else if (_isNull(value)) { ret = "null".fmt(value); } else if (_isUndefined(value)) { ret = "undefined"; } else if (_isArray(value)) { ret = "Array({0})".fmt(JSON.stringify(value)); } else if (_isFunction(value)) { ret = "Function({0})".fmt(value.name); } else if (_isRegExp(value)) { ret = "Regexp({0})".fmt(value.toString()); } else if (_isDate(value)) { ret = "Date({0})".fmt(value.toString()); } else if (_isError(value)) { ret = "Error({0})".fmt(value.toString()); } else if (_isNumberObj(value)) { ret = "Number({0})".fmt(value.toString()); } else if (_isStringObj(value)) { ret = "String("{0}")".fmt(value.toString()); } else if (_isBooleanObj(value)) { ret = "Boolean({0})".fmt(value.toString()); } else if (_isObject(value)) { ret = "Object({0})".fmt(JSON.stringify(value)); } else { ret = "Unknown"; } return ret; }
fmt函数是为了弥补javascript中字符串没有format函数的小缺憾。当然了,这是简化版,充其量就是个正则表达式替换,根本没有考虑显示宽度,显示精度等复杂的格式控制。
String.prototype.fmt = function(){ var args = arguments; return this.replace(/{(d+)}/g, function(m, i){ return args[i]; }); }
最后,附一个JSON.stringify函数的测试数据吧,别说我是冤枉它的:
var caseDatas = [ // number { sample : NaN, result : "null" }, { sample : 0, result : "0" }, { sample : +0, result : "0" }, { sample : -0, result : "0" }, { sample : 1, result : "1" }, { sample : 012, result : "10" }, { sample : 0x12, result : "18" }, { sample : 1.2, result : "1.2" }, { sample : 1.217e2, result : "121.7" }, { sample : Infinity, result : "null" }, { sample : -1, result : "-1" }, { sample : -012, result : "-10" }, { sample : -0x12, result : "-18" }, { sample : -1.2, result : "-1.2" }, { sample : -1.217e2, result : "-121.7" }, { sample : -Infinity, result : "null" }, // string { sample : "", result : '""' }, { sample : "abc", result : '"abc"' }, { sample : "1abc", result : '"1abc"' }, { sample : "NaN", result : '"NaN"' }, { sample : "0", result : '"0"' }, { sample : "+0", result : '"+0"' }, { sample : "-0", result : '"-0"' }, { sample : "1", result : '"1"' }, { sample : "012", result : '"012"' }, { sample : "0x12", result : '"0x12"' }, { sample : "1.2", result : '"1.2"' }, { sample : "1.217e2", result : '"1.217e2"' }, { sample : "Infinity", result : '"Infinity"' }, { sample : "-1", result : '"-1"' }, { sample : "-012", result : '"-012"' }, { sample : "-0x12", result : '"-0x12"' }, { sample : "-1.2", result : '"-1.2"' }, { sample : "-1.217e2", result : '"-1.217e2"' }, { sample : "-Infinity", result : '"-Infinity"' }, // boolean { sample : true, result : "true" }, { sample : false, result : "false" }, // remain primitive { sample : null, result : "null" }, { sample : undefined, result : undefined }, // built-in function { sample : new Object(), result : "{}" }, { sample : new Array(), result : "[]" }, { sample : new Function(), result : undefined }, { sample : new RegExp(), result : "{}" }, { sample : date, result : '"2016-12-18T00:00:00.000Z"' }, { sample : new Error(), result : "{}" }, { sample : new Number(), result : "0" }, { sample : new String(), result : '""' }, { sample : new Boolean(), result : "false" }, // built-in literals { sample : [], result : "[]" }, { sample : [1], result : "[1]" }, { sample : [1,2,3], result : "[1,2,3]" }, { sample : {}, result : "{}" }, { sample : person, result : '{"name":"wgs","age":18}' }, { sample : function() {}, result : undefined }, { sample : add, result : undefined }, ];