JavaScript中的类型检查
JS的动态类型有好有坏。好的一面,不必指明变量的类型。不好的是,咱们永远无法确定变量的类型。
typeof运算符可以确定JS中的6种类型:
typeof10;//=>'number' typeof'Hello';//=>'string' typeoffalse;//=>'boolean' typeof{a:1};//=>'object' typeofundefined;//=>'undefined' typeofSymbol();//=>'symbol'
同样,instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
classCat{} constmyCat=newCat(); myCatinstanceofCat;//=>true
但是typeof和instanceof的一些行为可能会令人混淆。防范于未然,咱们需要提前了解一些边缘情况。
1.typeofnull
typeofmyObject==='object'会告知myObject是否是一个对象类型。举个例子:
constperson={name:'前端小智'}; typeofperson;//=>'object'
typeofperson是'object',因为person是一个普通的JS对象。
在某场景下,变量值可能需要指定为null,下面是一些场景:
可以使用null来跳过指示配置对象
使用null初始化稍后要保存对象的变量
当函数由于某种原因无法构造对象时,返回null
例如,如果不存在正则表达式匹配项,则str.match(regExp)方法返回null:
constmessage='Hello'; message.match(/Hi/);//=>null
这里引出一个问题,可以使用typeof来区分有值的对象和具有null值的对象吗?
letmyObject=null; typeofmyObject;//=>'object' myObject={prop:'Value'}; typeofmyObject;//=>'object'
从上面可以看出,typeof对象有值的对象和具有null值的对象,得到的结果都是'object'。
可以如下面方法来检测变量是否有对象且不是null:
functionisObject(value){ returntypeofvalue==='object'&&value!==null; } isObject({});//=>true isObject(null);//=>false
除了检查value是否为object:typeofvalue==='object'之外,还需要明确地验证null:value!==null。
2.typeofarray
如果试图检测一个变量是否包含一个数组,常见的错误就是使用typeof操作符:
constcolors=['white','blue','red']; typeofcolors;//=>'object'
检测数组的正确方法是使用Array.isArray():
constcolors=['white','blue','red']; consthero={name:'Batman'}; Array.isArray(colors);//=>true Array.isArray(hero);//=>false
Array.isArray(colors)返回一个布尔值true,表示colors是一个数组。
3.虚值类型检查
JS中的undefined是一个特殊值,表示未初始化的变量。
如果试图访问未初始化的变量、不存在的对象属性,则获取到的值为undefined:
letcity; lethero={name:'前端小智',villain:false}; city;//=>undefined hero.age;//=>undefined
访问未初始化的变量city和不存在的属性hero.age的结果为undefined。
要检查属性是否存在,可以在条件中使用object[propName],这种遇到值为虚值或者undefined是不可靠的:
functiongetProp(object,propName,def){ //错误方式 if(!object[propName]){ returndef; } returnobject[propName]; } consthero={name:'前端小智',villain:false}; getProp(hero,'villain',true);//=>true hero.villain;//=>false
如果对象中不存在propName,则object[propName]的值为undefined。if(!object[propName]){returndef}保护缺少的属性。
hero.villain属性存在且值为false。但是,该函数在访问villan值时错误地返回true:getProp(hero,'villain',true)
undefined是一个虚值,同样false、0和''和null。
不要使用虚值作为类型检查,而是要明确验证属性是否存在于对象中:
typeofobject[propName]==='undefined' propNameinobject object.hasOwnProperty(propName)
接着,咱们来改进getProp()函数:
functiongetProp(object,propName,def){ //Better if(!(propNameinobject)){ returndef; } returnobject[propName]; } consthero={name:'前端小智',villain:false}; getProp(hero,'villain',true);//=>false hero.villain;//=>false
if(!(propNameinobject)){...}条件正确确定属性是否存在。
逻辑运算符
我认为最好避免使用逻辑运算符||作为默情况,这个容易打断阅读的流程:
consthero={name:'前端小智',villain:false}; constname=hero.name||'Unknown'; name;//=>'前端小智' hero.name;//=>'前端小智' //不好方式 constvillain=hero.villain||true; villain;//=>true hero.villain;//=>false
hero对象存在属性villain,值为false,但是表达式hero.villain||true结果为true。
逻辑操作符||用作访问属性的默认情况,当属性存在且具有虚值时,该操作符无法正确工作。
若要在属性不存在时默认设置,更好的选择是使用新的双问号(??)操作符,
consthero={name:'前端小智',villan:false}; //好的方式 constvillain=hero.villain??true; villain;//=>false hero.villain;//=>false
或使用解构赋值:
consthero={name:'前端小智',villain:false}; //Good const{villain=true}=hero; villain;//=>false hero.villain;//=>false
4.typeofNaN
整数,浮点数,特殊数字(例如Infinity,NaN)的类型均为数字。
typeof10;//=>'number' typeof1.5;//=>'number' typeofNaN;//=>'number' typeofInfinity;//=>'number'
NaN是在无法创建数字时创建的特殊数值。NaN是notanumber的缩写。
在下列情况下不能创建数字:
Number('oops');//=>NaN 5*undefined;//=>NaN Math.sqrt(-1);//=>NaN NaN+10;//=>NaN
由于NaN,意味着对数字的操作失败,因此对数字有效性的检查需要额外的步骤。
下面的isValidNumber()函数也可以防止NaN导致的错误:
functionisValidNumber(value){ //Good returntypeofvalue==='number'&&!isNaN(value); } isValidNumber(Number('Z99'));//=>false isValidNumber(5*undefined);//=>false isValidNumber(undefined);//=>false isValidNumber(Number('99'));//=>true isValidNumber(5+10);//=>true
除了typeofvalue==='number'之外,还多验证!isNaN(value)确保万无一失。
5.instanceof和原型链
JS中的每个对象都引用一个特殊的函数:对象的构造函数。
objectinstanceofConstructor是用于检查对象的构造函数的运算符:
constobject={}; objectinstanceofObject;//=>true constarray=[1,2]; arrayinstanceofArray;//=>true constpromise=newPromise(resolve=>resolve('OK')); promiseinstanceofPromise;//=>true
现在,咱们定义一个父类Pet和它的子类Cat:
classPet{ constructor(name){ this.name; } } classCatextendsPet{ sound='Meow'; } constmyCat=newCat('Scratchy');
现在,尝试确定myCat的实例
myCatinstanceofCat;//=>true myCatinstanceofPet;//=>true myCatinstanceofObject;//=>true
instanceof运算符表示myCat是Cat,Pet甚至Object的实例。
instanceof操作符通过整个原型链搜索对象的构造函数。要准确地检测创建对象的构造函数,需要检测constructor属性
myCat.constructor===Cat;//=>true myCat.constructor===Pet;//=>false myCat.constructor===Object;//=>false
只有myCat.constructor===Cat的计算结果为true,表示Cat是myCat实例的构造函数。
6.总结
运算符typeof和instanceof用于类型检查。它们尽管易于使用,但需要注意一些特殊情况。
需要注意的是:typeofnull等于'object'。要确定变量是否包含非null对象,需要显示指明null:
typeofmyObject==='object'&&myObject!==null
检查变量是否包含数组的最佳方法是使用Array.isArray(variable)内置函数。
因为undefined是虚值的,所以我们经常直接在条件句中使用它,但这种做法容易出错。更好的选择是使用propinobject来验证属性是否存在。
使用双问号操作系符号object.prop??def或者{prop=def}=object来访问可能丢失的属性。
NaN是一个类型为number的特殊值,它是由对数字的无效操作创建的。为了确保变量有正确的数字,最好使用更详细的验证:!isNaN(number)&&typeofnumber==='number'。
最后,请记住instanceof通过prototype链搜索实例的构造函数。如果不知道这一点,那么如果使用父类验证子类实例,可能会得到错误的结果。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log调试,这边顺便给大家推荐一个好用的BUG监控工具Fundebug。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。