JavaScript中的类型检查


Posted in Javascript onFebruary 03, 2020

JS 的动态类型有好有坏。好的一面,不必指明变量的类型。不好的是,咱们永远无法确定变量的类型。

typeof运算符可以确定 JS 中的6种类型:

typeof 10;    // => 'number'
typeof 'Hello';  // => 'string'
typeof false;   // => 'boolean'
typeof { a: 1 }; // => 'object'
typeof undefined; // => 'undefined'
typeof Symbol(); // => 'symbol'

同样,instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

class Cat { }
const myCat = new Cat();

myCat instanceof Cat; // => true

但是typeof和instanceof的一些行为可能会令人混淆。防范于未然,咱们需要提前了解一些边缘情况。

1. typeof null

typeof myObject === 'object'会告知myObject是否是一个对象类型。举个例子:

const person = { name: '前端小智' };

typeof person; // => 'object'

typeof person是'object',因为person是一个普通的 JS 对象。

在某场景下,变量值可能需要指定为 null,下面是一些场景:

可以使用null来跳过指示配置对象
使用null初始化稍后要保存对象的变量
当函数由于某种原因无法构造对象时,返回null
例如,如果不存在正则表达式匹配项,则str.match(regExp)方法返回null:

const message = 'Hello';
message.match(/Hi/); // => null

这里引出一个问题,可以使用typeof 来区分有值的对象和具有 null 值的对象吗?

let myObject = null;
typeof myObject; // => 'object'

myObject = { prop: 'Value' };
typeof myObject; // => 'object'

从上面可以看出,typeof 对象有值的对象和具有 null 值的对象,得到的结果都是'object'。

可以如下面方法来检测变量是否有对象且不是null:

function isObject(value) {
 return typeof value === 'object' && value !== null;
}

isObject({});  // => true
isObject(null); // => false

除了检查value是否为object: typeof value === 'object'之外,还需要明确地验证null: value !== null。

2. typeof array

如果试图检测一个变量是否包含一个数组,常见的错误就是使用typeof操作符:

const colors = ['white', 'blue', 'red'];

typeof colors; // => 'object'

检测数组的正确方法是使用Array.isArray():

const colors = ['white', 'blue', 'red'];
const hero = { name: 'Batman' };

Array.isArray(colors); // => true
Array.isArray(hero);  // => false

Array.isArray(colors)返回一个布尔值true,表示colors是一个数组。

3.虚值类型检查

JS中的undefined是一个特殊值,表示未初始化的变量。

如果试图访问未初始化的变量、不存在的对象属性,则获取到的值为 undefined :

let city;
let hero = { name: '前端小智', villain: false };

city;   // => undefined
hero.age; // => undefined

访问未初始化的变量 city 和不存在的属性hero.age的结果为undefined。

要检查属性是否存在,可以在条件中使用object[propName],这种遇到值为虚值或者undefined是不可靠的:

function getProp(object, propName, def) {
 // 错误方式
 if (!object[propName]) {
  return def;
 }
 return object[propName];
}

const hero = { name: '前端小智', villain: false };

getProp(hero, 'villain', true); // => true
hero.villain;          // => false

如果对象中不存在propName,则object [propName]的值为undefined。 if (!object[propName]) { return def }保护缺少的属性。

hero.villain属性存在且值为false。 但是,该函数在访问villan值时错误地返回true:getProp(hero, 'villain', true)

undefined是一个虚值,同样false、0和''和null。

不要使用虚值作为类型检查,而是要明确验证属性是否存在于对象中:

typeof object[propName] === 'undefined'
propName in object
object.hasOwnProperty(propName)

接着,咱们来改进getProp()函数:

function getProp(object, propName, def) {
 // Better
 if (!(propName in object)) {
  return def;
 }
 return object[propName];
}

const hero = { name: '前端小智', villain: false };

getProp(hero, 'villain', true); // => false
hero.villain;          // => false

if (!(propName in object)) { ... }条件正确确定属性是否存在。

逻辑运算符

我认为最好避免使用逻辑运算符||作为默情况,这个容易打断阅读的流程:

const hero = { name: '前端小智', villain: false };

const name = hero.name || 'Unknown';
name;   // => '前端小智'
hero.name; // => '前端小智'

// 不好方式
const villain = hero.villain || true;
villain;   // => true
hero.villain; // => false

hero 对象存在属性villain,值为 false,但是表达式hero.villain || true结果为true。

逻辑操作符||用作访问属性的默认情况,当属性存在且具有虚值时,该操作符无法正确工作。

若要在属性不存在时默认设置,更好的选择是使用新的双问号(??)操作符,

const hero = { name: '前端小智', villan: false };

// 好的方式
const villain = hero.villain ?? true;
villain;   // => false
hero.villain; // => false

或使用解构赋值:

const hero = { name: '前端小智', villain: false };

// Good
const { villain = true } = hero;
villain;   // => false
hero.villain; // => false

4. typeof NaN

整数,浮点数,特殊数字(例如Infinity,NaN)的类型均为数字。

typeof 10;    // => 'number'
typeof 1.5;   // => 'number'
typeof NaN;   // => 'number'
typeof Infinity; // => 'number'

NaN是在无法创建数字时创建的特殊数值。NaN是not a number的缩写。

在下列情况下不能创建数字:

Number('oops'); // => NaN

5 * undefined; // => NaN
Math.sqrt(-1); // => NaN

NaN + 10; // => NaN

由于NaN,意味着对数字的操作失败,因此对数字有效性的检查需要额外的步骤。

下面的isValidNumber()函数也可以防止NaN导致的错误:

function isValidNumber(value) {
 // Good
 return typeof value === 'number' && !isNaN(value);
}

isValidNumber(Number('Z99')); // => false
isValidNumber(5 * undefined); // => false
isValidNumber(undefined);   // => false

isValidNumber(Number('99')); // => true
isValidNumber(5 + 10);    // => true

除了typeof value === 'number'之外,还多验证!isNaN(value)确保万无一失。

5.instanceof 和原型链

JS 中的每个对象都引用一个特殊的函数:对象的构造函数。

object instanceof Constructor是用于检查对象的构造函数的运算符:

const object = {};
object instanceof Object; // => true

const array = [1, 2];
array instanceof Array; // => true

const promise = new Promise(resolve => resolve('OK'));
promise instanceof Promise; // => true

现在,咱们定义一个父类Pet和它的子类Cat:

class Pet {
 constructor(name) {
  this.name;
 }
}

class Cat extends Pet {
 sound = 'Meow';
}

const myCat = new Cat('Scratchy');

现在,尝试确定myCat的实例

myCat instanceof Cat;  // => true
myCat instanceof Pet;  // => true
myCat instanceof Object; // => 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 用于类型检查。 它们尽管易于使用,但需要注意一些特殊情况。

需要注意的是:typeof null等于'object'。 要确定变量是否包含非null对象,需要显示指明null:

typeof myObject === 'object' && myObject !== null
检查变量是否包含数组的最佳方法是使用Array.isArray(variable)内置函数。

因为undefined是虚值的,所以我们经常直接在条件句中使用它,但这种做法容易出错。更好的选择是使用prop in object来验证属性是否存在。

使用双问号操作系符号object.prop ?? def 或者 { prop = def } = object 来访问可能丢失的属性。

NaN是一个类型为number的特殊值,它是由对数字的无效操作创建的。为了确保变量有正确的数字,最好使用更详细的验证:!isNaN(number) && typeof number === 'number'。

最后,请记住instanceof通过prototype链搜索实例的构造函数。如果不知道这一点,那么如果使用父类验证子类实例,可能会得到错误的结果。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

Javascript 相关文章推荐
javascript游戏开发之《三国志曹操传》零部件开发(三)情景对话中仿打字机输出文字
Jan 23 Javascript
JavaScript日期时间与时间戳的转换函数分享
Jan 31 Javascript
JavaScript Window浏览器对象模型方法与属性汇总
Apr 20 Javascript
jquery trigger实现联动的方法
Feb 29 Javascript
JS实现点击登录弹出窗口同时背景色渐变动画效果
Mar 25 Javascript
微信小程序中使元素占满整个屏幕高度实现方法
Dec 14 Javascript
完美实现js焦点轮播效果(一)
Mar 07 Javascript
angular+ionic返回上一页并刷新页面
Aug 08 Javascript
Angular实现下拉框模糊查询功能示例
Jan 03 Javascript
vue中动态添加class类名的方法
Sep 05 Javascript
JS实现页面跳转与刷新的方法汇总
Aug 30 Javascript
在实例中重学JavaScript事件循环
Dec 03 Javascript
Vue的Eslint配置文件eslintrc.js说明与规则介绍
Feb 03 #Javascript
压缩Vue.js打包后的体积方法总结(Vue.js打包后体积过大问题)
Feb 03 #Javascript
24个解决实际问题的ES6代码片段(小结)
Feb 02 #Javascript
浅谈vuex为什么不建议在action中修改state
Feb 02 #Javascript
vuex+axios+element-ui实现页面请求loading操作示例
Feb 02 #Javascript
vue实现的封装全局filter并统一管理操作示例
Feb 02 #Javascript
node 版本切换的实现
Feb 02 #Javascript
You might like
php中的静态变量的基本用法
2014/03/20 PHP
php查询whois信息的方法
2015/06/08 PHP
Laravel 5.5 实现禁用用户注册示例
2019/10/24 PHP
php中用unset销毁变量并释放内存
2020/05/10 PHP
正负小数点后两位浮点数实现原理及代码
2013/09/06 Javascript
JS鼠标拖拽实例分析
2015/11/23 Javascript
js实现上一页下一页的效果【附代码】
2016/03/10 Javascript
jQuery事件处理的特征(事件命名机制)
2016/08/23 Javascript
JS实现的幻灯片切换显示效果
2016/09/07 Javascript
ionic由于使用了header和subheader导致被遮挡的问题的两种解决方法
2016/09/22 Javascript
原生javascript实现的ajax异步封装功能示例
2016/11/03 Javascript
jQuery中 $ 符号的冲突问题及解决方案
2016/11/04 Javascript
jquery日历插件e-calendar升级版
2016/11/10 Javascript
深入理解vue路由的使用
2017/03/24 Javascript
js实现多行文本框统计剩余字数功能
2017/03/28 Javascript
JS中使用正则表达式g模式和非g模式的区别
2017/04/01 Javascript
jQuery Autocomplete简介_动力节点Java学院整理
2017/07/17 jQuery
JS前端知识点 运算符优先级,URL编码与解码,String,Math,arguments操作整理总结
2019/06/27 Javascript
Vue axios与Go Frame后端框架的Options请求跨域问题详解
2020/03/03 Javascript
Angular短信模板校验代码
2020/09/23 Javascript
[58:37]Serenity vs Fnatic 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
巧用Python装饰器 免去调用父类构造函数的麻烦
2012/05/18 Python
python实现简单的计时器功能函数
2015/03/14 Python
Python实现给qq邮箱发送邮件的方法
2015/05/28 Python
Python语言描述最大连续子序列和
2017/12/05 Python
手把手教你如何安装Pycharm(详细图文教程)
2018/11/28 Python
python自定义函数实现一个数的三次方计算方法
2019/01/20 Python
Python API自动化框架总结
2019/11/12 Python
PyCharm配置anaconda环境的步骤详解
2020/07/31 Python
美国室内盆栽植物购买网站:Plants.com
2020/04/24 全球购物
销售心得体会
2014/01/02 职场文书
报关报检委托书
2014/04/08 职场文书
2014副局长群众路线对照检查材料思想汇报
2014/09/22 职场文书
业务员年终工作总结2015
2015/05/28 职场文书
《有余数的除法》教学反思
2016/02/22 职场文书
详解Flutter和Dart取消Future的三种方法
2022/04/07 Java/Android