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多线程的实现方法
May 08 Javascript
基于jQuery的弹出框插件
Mar 18 Javascript
jQuery实现的登录浮动框效果代码
Sep 26 Javascript
JS将滑动门改为选项卡(需鼠标点击)的实现方法
Sep 27 Javascript
基于JavaScript实现手机短信按钮倒计时(超简单)
Dec 30 Javascript
根据输入邮箱号跳转到相应登录地址的解决方法
Dec 13 Javascript
javascript学习之json入门
Dec 22 Javascript
完美实现js选项卡切换效果(二)
Mar 08 Javascript
vue+Java后端进行调试时解决跨域问题的方式
Oct 19 Javascript
vue中的provide/inject的学习使用
May 09 Javascript
基于vue开发微信小程序mpvue-docs跳转页面功能
Apr 10 Javascript
Layer UI表格列日期格式化及取消自动填充日期的实现方法
May 10 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
咖啡店都有些什么常规豆子呢?有什么风味在里面
2021/03/04 咖啡文化
一个PHP验证码类代码分享(已封装成类)
2011/07/17 PHP
php实现的简单日志写入函数
2015/03/31 PHP
PHP 应用容器化以及部署方法
2018/02/12 PHP
PHP基于redis计数器类定义与用法示例
2018/02/08 PHP
为jQuery.Treeview添加右键菜单的实现代码
2010/10/22 Javascript
用jquery修复在iframe下的页面锚点失效问题
2014/08/22 Javascript
javascript学习笔记(七)Ajax和Http状态码
2014/10/08 Javascript
JavaScript组件焦点与页内锚点间传值的方法
2015/02/02 Javascript
最全的Javascript编码规范(推荐)
2016/06/22 Javascript
Bootstrap的modal拖动效果
2016/12/25 Javascript
通过jsonp获取json数据实现AJAX跨域请求
2017/01/22 Javascript
localStorage的黑科技-js和css缓存机制
2017/02/06 Javascript
nodejs基于express实现文件上传的方法
2018/03/19 NodeJs
解决vue侦听器watch,调用this时出现undefined的问题
2020/10/30 Javascript
Python对两个有序列表进行合并和排序的例子
2014/06/13 Python
Python多进程池 multiprocessing Pool用法示例
2018/09/07 Python
django框架两个使用模板实例
2019/12/11 Python
django Model层常用验证器及自定义验证器详解
2020/07/15 Python
如何编写python的daemon程序
2021/01/07 Python
HTML5之SVG 2D入门2—图形绘制(基本形状)介绍及使用
2013/01/30 HTML / CSS
使用HTML5在网页中嵌入音频和视频播放的基本方法
2016/02/22 HTML / CSS
Lacoste美国官网:经典POLO衫品牌
2016/10/12 全球购物
Otel.com:折扣酒店预订
2017/08/24 全球购物
如何提高JDBC的性能
2013/04/30 面试题
物流管理应届生求职信
2013/11/07 职场文书
教育实习生的自我评价分享
2013/11/21 职场文书
幼儿园大班开学教师寄语
2014/04/03 职场文书
心理咨询承诺书
2014/05/20 职场文书
父亲节寄语大全
2015/02/27 职场文书
军训结束新闻稿
2015/07/17 职场文书
教学反思怎么写
2016/02/24 职场文书
python 下划线的多种应用场景总结
2021/05/12 Python
node.js使用express-fileupload中间件实现文件上传
2021/07/16 Javascript
python字典的元素访问实例详解
2021/07/21 Python
Python实现提取PDF简历信息并存入Excel
2022/04/02 Python