JavaScript: 数据类型检测
由于JavaScript是门松散类型语言,定义变量时没有类型标识信息,并且在运行期可以动态更改其类型,所以一个变量的类型在运行期是不可预测的,因此,数据类型检测在开发当中就成为一个必须要了解和掌握的知识点。
对于数据类型检测,实习新手会用typeof,老司机会用Object.prototype.toString.call();,在实际开发当中,后者可以说是目前比较好的办法了,可以准确地检测几种常见的内置类型,要比typeof靠谱得多。那么究竟类型检测都有哪些方法,各自都有哪些优劣呢,博主就借此机会来聊一聊数据类型检测的一些方法和其中的细节原理。
最早我们就使用下面的typeof方式检测一个值的类型:
var foo = 3; var type = typeof foo; // 或者 var type = typeof(foo);
后者看上去好像是一个函数调用,不过需要注意的是,typeof只是一个操作符,而不是函数,typeof后面的括号也不是函数调用,而是一个强制运算求值表达式,就相当于数学运算中的括号一样,最终返回一个运算结果,我们将上面的表达式分开就容易理解了:
var type = typeof (foo);
上面我们介绍到,初学者会用typeof判断一个值的类型,而老手们都踩过它的坑:
// 下面几个可以检测出准确的类型 typeof 3; // 'number'
typeof NaN; // 'number' typeof '3'; // 'string'
typeof ''; // 'string' typeof true; // 'boolean'
typeof Boolean(false); // 'boolean' typeof undefined; // 'undefined' typeof {}; // 'object' typeof function fn() {}; // 'function' // ES6中的Symbol类型
typeof Symbol(); // 'symbol' // ES6中的类本质上还是函数
typeof class C {}; // 'function' // 以下均不能检测出准确的类型 typeof null; // 'object' typeof new Number(3); // 'object'
typeof new String('3'); // 'object'
typeof new Boolean(true); // 'object' typeof []; // 'object' typeof /\w+/; // 'object' typeof new Date(); // 'object' typeof new Error(); // 'object' // ES6中的Map和Set
typeof new Map(); // 'object'
typeof new Set(); // 'object'
可以看到,对于基础类型,typeof还是比较准确的,而基础类型的包装类型就无法正确地检测了,只是笼统地返回一个'object',而对于ES6新添加的基础类型Symbol和数据结构对象Map&Set,也分别返回相应的类型值,但其中的Map和Set也是无法使用typeof检测其类型的。
Object和Function可以给出正确的类型结果,而其他像Array、RegExp、Date以及Error类型,无法得到正确的结果,同样只是得到了一个'object',这是由于它们都是继承自Object,其结果是,typeof操作符将Object和这几个类型混为一类,我们没有办法将他们区分开来。
比较特殊的是null,由于它是空对象的标记,所以会返回一个'object',站在开发者角度,这是完全错误的。最后需要注意的是,上面的NaN虽然是Not a Number,但它的确属于Number类型,这个有点滑稽,不过通常我们会用isNaN()方法来检测一个值是否为NaN。
所以,仅靠typeof是不能检测出以上所有类型,对于'object'的结果,通常我们都需要进一步的检测,以区分开不同的对象类型。目前流行的框架中,也不乏typeof的使用,所以说typeof并非一无是处,而是要适当地使用。
除了上面的typeof,还可以使用值的构造器,也就是利用constructor属性来检测其类型:
(3).constructor === Number; // true NaN.constructor === Number; // true ''.constructor === String; // true true.constructor === Boolean; // true Symbol().constructor === Symbol; // true var o = {};
o.constructor === Object; // true var fn = function() {};
fn.constructor === Function; // true var ary = [];
ary.constructor === Array; // true var date = new Date();
date.constructor === Date; // true var regex = /\w+/;
regex.constructor === RegExp; // true var error = new Error();
error.constructor === Error; // true var map = new Map();
map.constructor === Map; // true var set = new Set();
set.constructor === Set; // true
从上面的结果来看,利用constructor属性确实可以检测大部分值的类型,对于基础类型也同样管用,那为什么基础类型也有构造器呢,这里其实是对基础类型进行了隐式包装,引擎检测到对基础类型进行属性的存取时,就对其进行自动包装,转为了对应的包装类型,所以上面的基础类型结果最终的代码逻辑为:
new Number(3).constructor === Number; // true new Number(NaN).constructor === Number; // true new String('').constructor === String; // true new Boolean(true).constructor === Boolean; // true
需要注意的是,我们对基础类型的数字3进行属性的存取时,使用了一对括号,这是因为,如果省略了括号而直接使用3.constructor,引擎会尝试解析一个浮点数,因此会引发一个异常。另外,我们没有列举null和undefined的例子,这是因为,null和undefined没有对应的包装类型,因此无法使用constructor进行类型检测,尝试访问constructor会引发一个异常,所以,constructor无法识别null和undefined。不过我们可以先利用其他手段检测null和undefined,然后对其他类型使用构造器检测,就像下面这样:
/**
* 利用contructor检测数据类型
*/
function is(value, type) {
// 先处理null和undefined
if (value == null) {
return value === type;
}
// 然后检测constructor
return value.constructor === type;
} var isNull = is(null, null); // true
var isUndefined = is(undefined, undefined); // true
var isNumber = is(3, Number); // true
var isString = is('3', String); // true
var isBoolean = is(true, Boolean); // true var isSymbol = is(Symbol(), Symbol); // true var isObject = is({}, Object); // true
var isArray = is([], Array); // true
var isFunction = is(function(){}, Function); // true
var isRegExp = is(/./, RegExp); // true
var isDate = is(new Date, Date); // true
var isError = is(new Error, Error); // true var isMap = is(new Map, Map); // true
var isSet = is(new Set, Set); // true
除了上面的常规类型,我们还可以使用它检测自定义对象类型:
function Animal() {} var animal = new Animal(); var isAnimal = is(animal, Animal); // true
但是涉及到对象的继承时,构造器检测也有些力不从心了:
function Tiger() {} Tiger.prototype = new Animal();
Tiger.prototype.constructor = Tiger; var tiger = new Tiger(); var isAnimal = is(tiger, Animal); // false
我们也看到了,在上面的对象继承中,Tiger原型中的构造器被重新指向了自己,所以我们没有办法检测到它是否属于父类类型。通常这个时候,我们会使用instanceof操作符:
var isAnimal = tiger instanceof Animal; // true
instanceof也可以检测值的类型,但这仅限于对象类型,而对于对象类型之外的值来说,instanceof并没有什么用处。undefined显然没有对应的包装类型,null虽然也被typeof划分为'object',但它并不是Object的实例,而对于基础类型,instanceof并不会对其进行自动包装:
// 虽然typeof null的结果为'object' 但它并不是Object的实例 null instanceof Object; // false // 对于基础类型 instanceof操作符并不会有隐式包装 3 instanceof Number; // false '3' instanceof Number; // false true instanceof Boolean; // false Symbol() instanceof Symbol; // false // 只对对象类型起作用 new Number(3) instanceof Number; // true new String('3') instanceof String; // true new Boolean(true) instanceof Boolean; // true Object(Symbol()) instanceof Symbol; // true ({}) instanceof Object; // true [] instanceof Array; // true (function(){}) instanceof Function; // true /./ instanceof RegExp; // true new Date instanceof Date; // true new Error instanceof Error; // true new Map instanceof Map; // true new Set instanceof Set; // true
很遗憾,我们没有办法使用instanceof来检测基础类型的值了,如果非要使用,前提是先要将基础类型包装成对象类型,这样一来就必须使用其他方法检测到这些基础类型,然后进行包装,这样做毫无意义,因为我们已经获取到它们的类型了。所以,除了对象类型之外,不要使用instanceof操作符来检测类型。
最后来说一说如何利用Object中的toString()方法来检测数据类型。通常,我们会使用下面两种形式获取到Object的toString方法:
var toString = ({}).toString; // 或者 var toString = Object.prototype.toString;
推荐使用后者,获取对象原型中的toString()方法。下面我们来看看它是如何获取到各种值的类型的:
toString.call(undefined); // '[object Undefined]' toString.call(null); // '[object Null]' toString.call(3); // '[object Number]' toString.call(true); // '[object Boolean]' toString.call(''); // '[object String]' toString.call(Symbol()); // '[object Symbol]' toString.call({}); // '[object Object]' toString.call([]); // '[object Array]' toString.call(function(){}); // '[object Function]' toString.call(/\w+/); // '[object RegExp]' toString.call(new Date); // '[object Date]' toString.call(new Error); // '[object Error]' toString.call(new Map); // '[object Map]' toString.call(new Set); // '[object Set]'
从代码中可以看到,不管是基础类型还是对象类型,均会的到一个包含其类型的字符串,null和undefined也不例外,它们看上去好像有了自己的“包装类型”,为什么Object中的toString()方法这么神奇呢,归根结底,这都是ECMA规范规定的,历来的规范中都对这个方法有所定义,而最为详尽的,当属最新的ES6规范了,下面是ES6中关于Object原型中toString()方法的规定:
其主要处理方式为:如果上下文对象为null和undefined,返回"[object Null]"和"[object Undefined]",如果是其他值,先将其转为对象,然后一次检测数组、字符串、arguments对象、函数及其它对象,得到一个内建的类型标记,最后拼接成"[object Type]"这样的字符串。
看上去这个方法相当的可靠,利用它,我们就可以把它们当成普通基础类型一起处理了,下面我们封装一个函数,用于判断常见类型:
// 利用Object#toString()检测类型 var _toString = Object.prototype.toString; function is(value, typeString) {
// 获取到类型字符串
var stripped = _toString.call(value).replace(/^\[object\s|\]$/g, '');
return stripped === typeString;
} is(null, 'Null'); // true
is(undefined, 'Undefined'); // true
is(3, 'Number'); // true
is(true, 'Boolean'); // true
is('hello', 'String'); // true
is(Symbol(), 'Symbol'); // true
is({}, 'Object'); // true
is([], 'Array'); // true
is(function(){}, 'Function'); // true
is(/\w+/, 'RegExp'); // true
is(new Date, 'Date'); // true
is(new Error, 'Error'); // true
is(new Map, 'Map'); // true
is(new Set, 'Set'); // true
虽然上面常见类型都能被正确识别,但Object#toString()方法也不是万能的,它不能检测自定义类型:
function Animal() {} var animal = new Animal(); var isAnimal = is(animal, 'Animal'); // false ({}).toString.call(animal); // '[object Object]'
从这一点来看,相比较constructor方式也还有点逊色,所以Object#toString()方法也不是万能的,遇到自定义类型时,我们还是得依赖instanceof来检测。
上面介绍了这么多,总体来讲,可以归纳为下面几点:
1. Object#toString()和改进后的constructor方式覆盖的类型较多,比较实用
2. 如果要检测一个变量是否为自定义类型,要使用instanceof操作符
3. 也可以有选择地使用typeof操作符,但不要过分依赖它
本文完。
参考资料:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
http://www.ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring
http://tobyho.com/2011/01/28/checking-types-in-javascript/
http://javascript.crockford.com/remedial.html
http://javascript.info/tutorial/type-detection
JavaScript: 数据类型检测的更多相关文章
- JavaScript 数据类型检测总结
JavaScript 数据类型检测总结 原文:https://blog.csdn.net/q3254421/article/details/85483462 在js中,有四种用于检测数据类型的方式,分 ...
- javascript 数据类型 -- 检测
一.前言 在上一篇博文中 Javascript 数据类型 -- 分类 中,我们梳理了 javascript 的基本类型和引用类型,并提到了一些冷知识.大概的知识框架如下: 这篇博文就讲一下在写代码的过 ...
- JavaScript数据类型检测 数组(Array)检测方式
前言 对于确定某个对象是不是数组,一直是数组的一个经典问题.本文专门将该问题择出来,介绍什么才是正确的javascript数组检测方式 typeof 首先,使用最常用的类型检测工具--typeof运算 ...
- Javascript数据类型检测
Javascript有5种简单数据类型和一种复杂数据类型 基本数据类型:String,Boolean,Number,Undefined, Null 引用数据类型:Object(Array,Date,R ...
- JavaScript数据类型检测详解
//JS该如何检测数据的类型呢? //使用关键字: typeof //输出结果依次为:'number','string','boolean'. console.log(typeof 17); cons ...
- javascript数据类型检测方法
一.字符串.数字.布尔值.undefined的最佳选择市使用 typeof 运算符进行检测: 对于字符串,typeof 返回"string" 对于数字,typeof 返回" ...
- JavaScript系列文章:不能不看的数据类型检测
由于JavaScript是门松散类型语言,定义变量时没有类型标识信息,并且在运行期可以动态更改其类型,所以一个变量的类型在运行期是不可预测的,因此,数据类型检测在开发当中就成为一个必须要了解和掌握的知 ...
- javascript 中检测数据类型的方法
typeof 检测数据类型 javascript 中检测数据类型有好几种,其中最简单的一种是 typeof 方式.typeof 方法返回的结果是一个字符串.typeof 的用法如下: typeof v ...
- 【JavaScript框架封装】数据类型检测模块功能封装
数据类型检测封装后的最终模块代码如下: /*数据类型检验*/ xframe.extend(xframe, { // 鸭子类型(duck typing)如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭 ...
随机推荐
- Python3基础 from...import 局部导入
Python : 3.7.3 OS : Ubuntu 18.04.2 LTS IDE : pycharm-community-2019.1.3 ...
- XLNet and Robertra
XLNET But the AE language model also has its disadvantages. It uses the [MASK] in the pretra ...
- 修复Nginx报错:upstream sent too big header while reading response header from upstream
在 nginx.conf 的http段,加入下面的配置: proxy_buffer_size 128k; proxy_buffers 32k; proxy_busy_buffers_size 128k ...
- 1.2 lvm镜像卷
镜像能够分配物理分区的多个副本,从而提高数据的可用性.当某个磁盘发生故障并且其物理分区变为不可用时,您仍然可以访问可用磁盘上的镜像数据.LVM 在逻辑卷内执行镜像. 系统版本: # cat /etc ...
- python读取excel数据并以第一行标题加内容组成字典格式返回
excel结构如图所示: 代码: import xlrd ''' 通用获取excel数据 @:param path excel文件路径 @:param sheet_name excel文件里面shee ...
- 前端与算法 leetcode 66. 加一
目录 # 前端与算法 leetcode 66. 加一 题目描述 概要 提示 解析 解法一 解法二 算法 # 前端与算法 leetcode 66. 加一 题目描述 给定一个由整数组成的非空数组所表示的非 ...
- PHP 跨域资源共享 CORS 设定
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从 ...
- 002 spring boot框架,引入mybatis-generator插件,自动生成Mapper和Entity
1.创建一个springboot项目 2.创建项目的文件结构以及jdk的版本 3.选择项目所需要的依赖 点击next,直到项目构建完成. 4.项目初步结构 5.POM文件 <?xml versi ...
- 第3课,python使用for循环
前言: 学习了python的while循环后感觉循环是挺强大的.下面学习一个更智能,更强大的循环-- for循环. 课程内容: 1.由while循环,到for循环,格式和注意项 2.for循环来报数 ...
- 【LEETCODE】73、根据身高重建队列 第406题
说实话,这道题我没想出来,但是看解题报告题解比较让人觉得眼前一亮,这里记录下来 package y2019.Algorithm.greedy.medium; import java.util.Arra ...