JS变量类型检测最佳实践

  JavaScript最初的目的,只是为网页浏览增加基本交互和表单验证。现在拿它来干工程化,来干富应用,就容易出各种幺蛾子。(Brendan Eich花10天就捣鼓出来的语言,他都没指望什么,你还能指望什么。。。)

  JavaScript是一门弱类型语言。弱类型,只是有类型检测不严格,容忍隐式类型转换等特性,不代表没有类型。在模块化编程时,函数只接受输入输出,类型检测就变得尤为重要。

  为了避免JavaScript本身的问题,开发者甚至搞出了TypeScript这个带静态类型检查的语言。至于JavaScript有多烂,下面给你张图,你自己体会。(笑)

JavaScript冷笑话

  这里就简单总结下, JS变量类型检测的最佳实践。(参考自《编写可维护的JavaScript》)

先说结论:

如果你要判断的是原始类型或JavaScript内置对象,使用Object.prototype.toString.call(variable);

如果要判断的是自定义类型,请使用instanceof

类型检测分类

JS中的变量,按照存储的是值还是引用,可以分为两大类:

  • 原始类型:字符串、数字、布尔值、null、undefined
  • 引用类型:原始类型以外的所有类型,例如Object、Array、Date、Error、Function等

类型检测方法

检测原始值

  使用typeof运算符,检测除null外的四种类型,都十分安全高效,即使将其用于一个未声明的对象也不会报错。

  注意:不要使用typeof判断null,因为返回的是’object’。如果一定要判断,请使用恒等运算符(===)或者非恒等运算符(!==)。使用场景目前我只遇到两个,都是在null为可预期的结果时:

  1. 预期赋值为对象的变量,null通常作为其“占位符”。
  2. 使用document.getElementById()等DOM选择API时,选择集为空时,返回为null。

检测引用值

  检测引用值的最好做法是使用instanceof运算符,但是这个运算符不仅会检查构造器(constructor),还会向上查找原型链(prototype)。由于每个对象都被认为是Object的实例,因此引用类型的 instanceof Object 都返回true,这一点也算是该方法的一个短板,后面的通用方法可以解决这个问题。

  注意:在JS中检测自定义类型时,唯一的方法,就是instanceof。

检测属性是否存在

  检测属性是否存在,应该使用in运算符,而不是使用假值检测。否则一旦属性值为0、空字符串、false、null、undefined等假值,会导致检测出错。比如属性记录了一个数字,这个数字恰好为0,那么在if语句和三元表达式以及其他形式的强制转换布尔值的方法中,object[propertyName]会返回false,然而属性是存在的。

  检测实例上的属性,用hasOwnProperty()方法,如果这个属性存在于原型上,则返回false。

  注意:所有继承自Object的对象都有这个方法,但IE8及更早版本IE中,DOM对象并非继承自Object,不包含这个方法。如果硬要判断的话,需要先用in语句判断下。

通用检测方法

  感谢Kangax,提出了一种非常优雅的类型检测方法,

1
Object.prototype.toString.call(variable)

  这种方法在所有浏览器下,都会返回统一格式的标准字符串结果,以下是测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function diyClass(){}
var variables = ['string',100,true,null,,{},[],JSON,new Date(),new Error(),diyClass,new diyClass()];
for(var i = 0;i<variables.length;i++){
var typeNameString = Object.prototype.toString.call(variables[i]);
console.log(typeNameString);
}
/* 控制台输出:
[object String]
[object Number]
[object Boolean]
[object Null]
[object Undefined]
[object Object]
[object Array]
[object JSON]
[object Date]
[object Error]
[object Function]
[object Object] // 这就是为什么判断自定义类型需要使用instanceof
*/

  综上所述,一个健壮的类型检测函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 用于检查变量类型的函数
* @param {String} expectedType 期望的类型
* @param {???} variable 待检测变量
* @return {Boolean} 检测结果,符合为true
*/
function is(expectedType, variable) {
/* 异常捕获 */
if (typeof expectedType !== 'string') {
throw new Error({message:'类型名称应为字符串'});
}

/* 取得类型元数据 */
var variableType = Object.prototype.toString.call(variable);

/* 查找以']'结尾的整词,将元数据转化为短字符串,具体有两种方法 */

// 1、正则表达式法,具体释义参照以下网址
// https://regexper.com/#%2F%5Cb%5BA-z%5D%2B%5Cb(%3F%3D%5C%5D)%2Fg
variableType = variableType.match(/\b[A-z]+\b(?=\])/g)[0];
// 2、字符串拆分法比较蠢,但是也在这里写下
// variableType = variableType.split(' ').pop().split(']').shift();

// 比较转为小写的类型字符串是否全等
return expectedType.toLowerCase() === variableType.toLowerCase();
}

  用这个方法可以较为准确地判断变量的类型,包括原始值、引用值。