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 相关文章推荐
网页里控制图片大小的相关代码
Jun 13 Javascript
让 JavaScript 轻松支持函数重载 (Part 2 - 实现)
Aug 04 Javascript
JS操作Cookies包括(读取添加与删除)
Dec 26 Javascript
Jquery实现侧边栏跟随滚动条固定(兼容IE6)
Apr 02 Javascript
Node.js 制作实时多人游戏框架
Jan 08 Javascript
详解A标签中href=""的几种用法
Aug 20 Javascript
vscode 开发Vue项目的方法步骤
Nov 25 Javascript
浅谈redux, koa, express 中间件实现对比解析
May 23 Javascript
Node.JS枚举统计当前文件夹和子目录下所有代码文件行数
Aug 23 Javascript
layer关闭弹出窗口触发表单提交问题的处理方法
Sep 25 Javascript
JS实现简单随机3D骰子
Oct 24 Javascript
Vue filter 过滤当前时间 实现实时更新效果
Dec 20 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
ThinkPHP提交表单时默认自动转义的解决方法
2014/11/25 PHP
filemanage功能中用到的common.js
2007/04/08 Javascript
jQuery中change事件用法实例
2014/12/26 Javascript
javascript 实现 原路返回
2015/01/21 Javascript
JQuery中Bind()事件用法分析
2015/05/05 Javascript
Bootstrap 网站实例之单页营销网站
2016/10/20 Javascript
Html5 js实现手风琴效果
2020/04/17 Javascript
angularjs ocLazyLoad分步加载js文件实例
2017/01/17 Javascript
NodeJs的fs读写删除移动监听
2017/04/28 NodeJs
Javascript实现页面滚动时导航智能定位
2017/05/06 Javascript
js读取本地文件的实例
2017/12/22 Javascript
Vue下路由History模式打包后页面空白的解决方法
2018/06/29 Javascript
js判断在哪个浏览器打开项目的方法
2020/01/21 Javascript
微信小程序去除左上角返回键的实现方法
2020/03/06 Javascript
JavaScript实现无限轮播效果
2020/11/19 Javascript
[06:45]DOTA2卡尔工作室 英雄介绍幻影长矛手篇
2013/07/12 DOTA
[59:35]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#1COL VS Alliance第二局
2016/03/04 DOTA
[01:18:33]Secret vs VGJ.S Supermajor小组赛C组 BO3 第一场 6.3
2018/06/04 DOTA
Python编程中的反模式实例分析
2014/12/08 Python
Python数据类型详解(三)元祖:tuple
2016/05/08 Python
python魔法方法-自定义序列详解
2016/07/21 Python
Python并行分布式框架Celery详解
2018/10/15 Python
关于Python3爬虫利器Appium的安装步骤
2020/07/29 Python
python实现学生通讯录管理系统
2021/02/25 Python
CSS3 对过渡(transition)进行调速以及延时
2020/10/21 HTML / CSS
使用iframe+postMessage实现页面跨域通信的示例代码
2020/01/14 HTML / CSS
布鲁明戴尔百货店:Bloomingdale’s
2016/12/21 全球购物
性能测试工程师的面试题
2015/02/20 面试题
水利学院求职自荐书
2014/02/01 职场文书
《可爱的动物》教学反思
2014/02/22 职场文书
公开服务承诺制度
2014/03/26 职场文书
求职简历自我评价范文
2015/03/10 职场文书
一个独生女的故事观后感
2015/06/04 职场文书
2016十一国庆节慰问信
2015/12/01 职场文书
Python 把两层列表展开平铺成一层(5种实现方式)
2021/04/07 Python
一次Mysql update sql不当引起的生产故障记录
2022/04/01 MySQL