一、前言

  在上一篇博文中 Javascript 数据类型 -- 分类 中,我们梳理了 javascript 的基本类型和引用类型,并提到了一些冷知识。大概的知识框架如下:

  这篇博文就讲一下在写代码的过程中,通常怎么检测这些类型。

二、检测

  总的来说,我们有4种可检测数据类型的方法, typeof 运算符、  constructor 属性、 instanceof 运算符、 prototype.isPrototypeOf 方法、 Object.prototype.toString.call 方法、 in 操作符、 hasOwnProperty 方法及 isNaN() 、 Array.isArray() 等几种特殊检测方式。每种方法各有优劣,实际运用时还要结合着使用。

  typeof 运算符

    在上一篇博文中,我们已经用到了 typeof 运算符来检测类别,这里就简单得总结一下它的结果和结论。

typeof '123'                    // string
typeof String('123') // string
typeif new String('123') // string typeof 123 // number
typeof Number(23.4) // number
typeof new Number(2.3e4) // number
typeof NaN // number
typeof Infinity // number typeof false // boolean
typeof Boolean(false) // boolean
typeof new Boolean(false) // boolean var mySymbol = Symbol()
typeof mySymbol // symbol var a;
typeof a // undefined,定义但未初始化
typeof b // undefined,未定义 var div = document.getElementById('abc')
console.log(div) // null
typeof div // object var Fn = function () {}
var classC = class C {}
typeof Fn // function
typeof classC // function,类在本质上还是函数 typeof {} // object
typeof [] // object
typeof /\w+/ // object
typeof new Map() // object
typeof new Set() // object (function () {
  return typeof arguments
})()                 // object

    如上所示, typeof 运算符的语法是 typeof variable 。因是运算符的原由,所以不推荐使用加括号的写法,即( typeof (variable) ) ,以免与方法混淆。运算后的返回值有:

    • "boolean"     :布尔型;
    • "string"      :字符型;
    • "number"      :数值型(整数/浮点数值/NaN);
    • "symbol"     :Symbol 型
    • "undefined" :变量未定义,或未初始化;
    • "object"     :引用型或null;
    • "function"  :函数型;

    由上可知, typeof 运算符对于大部分基本类型的判断还是准确的,特别是 string 、 number 、 boolean 和 symbol ,对于大部分的引用类似会返回 object ,函数虽然也属于引用类型,但由于其具有很对有别于一般引用类型的特性,所以单独判断为 "function" 。若是针对 NaN 进行检测,可以使用全局方法 isNaN(variable) 更为精准。由此,我们可以编写一个统一的方法,先将 undefined 和 null 类型检测出来,再检测其他基本类型。

function is(value, type) {
if (value == null) {
return value === type
} else if (typeof value === 'number' && type === 'isNaN') {
return isNaN(value)
}
return typeof value === type
} var x;
is(x, undefined) // true var div = document.getElementById('div')
is(div, null) // true var y = 2 + undefined
is(y, 'isNaN') // true is(3, 'number') // true
is('3', 'string') // true
is(true, 'boolean') // true
is(Symbol(), 'symbol') // true
is(function(){}, 'function') // true is(new String('s'), 'object') // true
is(new Map(), 'object') // true
is({}, 'object') // true

  

  constructor 属性

    由于 typeof 运算符并不能准确地检测出引用类型的值,所以我们可以试试 constructor 属性。

    当一个函数  foo 被定义是,javascript 会给 foo 函数添加 prototype 原型,然后再在 prototype 上添加一个 contructor 属性,并让其指向 foo 的引用,如下所示:

    当执行 var bar = new foo() 时, foo 被当成了构造函数, bar 是 foo 的实例对象,此时 foo 原型上的 constructor 传递到了 bar 上,因此 bar.constructor == foo (返回的是一个true)

    可以看出,JavaScript在函数 foo 的原型上定义了 constructor ,当 foo 被当作构造函数用来创建对象时,创建的新对象就被标记为 foo 类型,使得新对象有名有姓,可以追溯。所以 constructor 属性可以用来检测自定义类型。同理,JavaScript中的数据类型也遵守这个规则:

'3'.constructor === String                 // true,由于包装对象的缘故,而拥有了 construtor 属性
String('').constructor === String // true
new String('').constructor === String // true (3).constructor === Number // true,与 string 同理
Number(3).constructor === Number // true
new Number(3).constructor === Number // true false.constructor === Boolean // true,与 string 同理
Boolean(false).constructor === Boolean // true
new Boolean().constructor === Boolean // true Symbol().constructor === Symbol // true ({}).constructor == Object // true
[].constructor === Array // true
(function(){}).constructor === Function // true
new Date().constructor === Date // true
new Error().constructor === Error // true
new RegExp().constructor === RegExp // true
new Map().constructor === Map // true
new Set().constructor === Set // true document.constructor === HTMLDocument // true
window.constructor === Window // true null.constructor === Object // Uncaught TypeError: Cannot read property 'constructor' of null
undefined.constructor === Object // Uncaught TypeError: Cannot read property 'constructor' of undefined

    由上可见, constructor 属性对于大部分值都是能准确得出其类型的,特别是几个基本类型,也得益于包装对象的缘故,可以被准确地检测出来。只有 undefined 和 null 由于没有包装对象,不能进行转换,所以不能被检出。因此,上面的判断函数可以改进为:

function is(value, type) {
if (value == null) {
return value === type
// } else if (value.constructor === 'number' && type === 'isNaN') {
} else if (value.constructor === Number && type === 'isNaN') {
return isNaN(value)
}
// return typeof value === type
return value.constructor === type
} var x;
is(x, undefined) // true var div = document.getElementById('div')
is(div, null) // true var y = 2 + undefined
is(y, 'isNaN') // true is(3, Number) // true
is('3', String) // true
is(true, Boolean) // true
is(Symbol(), Symbol) // true
is({}, Object) // true
is([], Array) // true
is(new Map(), Map) // true
is(new Set(), Set) // true
is(new Date(), Date) // true
is(new Error(), Error) // true
is(new RegExp(), RegExp) // true
is(function(){}, Function) // true
is(document, HTMLDocument) // true
is(window, Window) // true

  当然,根据 construtor 方法的原理可以知道,在判断自定义类型时,由于 prototype 是可以被认为修改的,原有的 construtor 也就会被指向新 prototype 的构造器上。因此,为了规范,在重新定义原型时,一定要给 constructor 重新赋值,以保证构造器不被改变。除非是有预期的希望它改变。

function Animal(){}

var cat = new Animal()
is(cat, Animal) // true Animal.prototype = {}
var dog = new Animal()
is(dog, Animal) // false
is(dog, Object) // true Animal.prototype.constructor = Animal
var tiger = new Animal()
is(tiger, Animal) // true
is(tiger, Object) // false

  instanceof 运算符

    在上一节中我们说到,如果手动更改构造函数的 prototpye 的话, constructor 方法就会失效。这种情况下,除了手动为构造函数指定 constructor 外,还有一种方法就是使用 instanceof 运算符。

cat instanceof Animal         // false
dog instanceof Animal // true
tiger instanceof Animal // true

    在上面的代码中, cat instanceof Animal 输出的是  false ,为什么呢?

    这是因为 instanceof 检测是是对象的原型链中是否包含某个构造函数的 prototype 属性。即 instanceof 检测的是原型。在上面的代码中, Animal 在 cat 实例化之后,将 prototype 属性改成 {},所以  cat._proto_ 中,已经不包括 Animal.prototype 了,所以输出为 false 。所以  instanceof 运算符也存在自定义类型的继承问题。但对于没被修改过 prototype 属性的内置对象而言, instanceof 方法还是可以判断的。

({}) instanceof Object;               // true
[] instanceof Array; // true
(function(){}) instanceof Function; // true
/\w+/ instanceof RegExp; // true
new Date instanceof Date; // true
new Error instanceof Error; // true
new Map instanceof Map; // true
new Set instanceof Set; // true // 由于默认情况下,对象都是继承自 Object,所以引用类型都是 Object 的实例。
[] instanceof Object // true

    由于 instanceof  是根据原型链来进行检查的,所以适用于任何引用类型。而基础类型并没有原型,所以并不能检测出来。对于有包装对象的几个继承类型, instanceof 也不会隐性转换,除非用包装对象进行显性转换才可检测出来。

3 instanceof Number;                  // false
Number(3) instanceof Number // false
new Number(3) instanceof Number // true '3' instanceof String; // false
String('3') instanceof String // false
new String('3') instanceof String // true true instanceof Boolean; // false
Boolean(true) instanceof Boolean // false
new Boolean(true) instanceof Boolean // true Symbol() instanceof Symbol; // false
Object(Symbol()) instanceof Symbol // true // 特别的,虽然 typeof null 等于 object,但 null 并不是 object 的实例
null instanceof Object; // false

  prototype.isPrototypeOf() 方法

    方法用于测试一个对象是否存在于另一个对象的原型链上,用法为 XXX.prototype.isPrototypeOf(instance) 。

Object.prototype.isPrototypeOf({});                  // true
Array.prototype.isPrototypeOf([]); ; // true
Function.prototype.isPrototypeOf(function(){}); // true
RegExp.prototype.isPrototypeOf(/\w+/); // true
Date.prototype.isPrototypeOf(new Date); // true
Error.prototype.isPrototypeOf(new Error); // true
Map.prototype.isPrototypeOf(new Map); // true
Set.prototype.isPrototypeOf(new Set); // true // 由于默认情况下,对象都是继承自 Object,所以引用类型都是 Object 的实例。
Object.prototype.isPrototypeOf(function(){}) // true Number.prototype.isPrototypeOf(3); // false
Number.prototype.isPrototypeOf(Number(3)); // false
Number.prototype.isPrototypeOf(new Number(3)); // true String.prototype.isPrototypeOf('3'); // false
String.prototype.isPrototypeOf(String('3')); // false
String.prototype.isPrototypeOf(new String('3')); // true Boolean.prototype.isPrototypeOf(true); // false
Boolean.prototype.isPrototypeOf(Boolean(true); // false
Boolean.prototype.isPrototypeOf(new Boolean(true)); // true Symbol.prototype.isPrototypeOf(Symbol()); // false
Symbol.prototype.isPrototypeOf(Object(Symbol())); // true // 特别的,虽然 typeof null 等于 object,Object 并不在 null 的原型链上
Object.prototype.isPrototypeOf(null); // false

    由上可知, prototype.isPrototypeOf() 方法与 instanceof 操作符走的是互相逆向的两条路, instanceof 是从实例出发,查找实例上是否有某构造函数的 prototype 属性,而 prototype.isPrototypeOf() 则是从构造函数出发,寻找该构造函数是否在某个已存在的实例的原型链上。

    故而,如果 A instanceof B 为真,则 B.prototype.isPrototypeOf(A) 也一定为真。

  Object.prototype.toString.call() 方法

    在最新的ES6规范中,关于 Object.prototype.toString() 方法是这么规定的:

    也就是说,如果上下文对象为 null 和 undefined ,返回 "[object Null]" 和 "[object Undefined]" ,如果是其他值,先将其转为对象,然后依次检测数组、字符串、arguments对象、函数及其它对象,得到一个内建的类型标记,最后拼接成 "[object Type]" 这样的字符串。由此可见, Object.prototype.toString.call() 方法是根正苗红的用来检测内置对象类型的方法。

var _toString = Object.prototype.toString;

function is(value, typeString) {
// 获取到类型字符串
var stripped = _toString.call(value).replace(/^\[object\s|\]$/g, '');
if (stripped === 'Number' && typeString === 'isNaN') {
return isNaN(value);
}
return stripped === typeString;
} var x;
is(x, 'Undefined') // true var div = document.getElementById('div')
is(div, 'Null') // true var y = 2 + undefined
is(y, 'isNaN') // true is(3, 'Number') // true
is('3', 'String') // true
is(true, 'Boolean') // true
is(Symbol(), 'Symbol') // true
is({}, 'Object') // true
is([], 'Array') // true
is(new Map(), 'Map') // true
is(new Set(), 'Set') // true
is(new Date(), 'Date') // true
is(new Error(), 'Error') // true
is(new RegExp(), 'RegExp') // true
is(function(){}, 'Function') // true
is(document, 'HTMLDocument') // true
is(window, 'Window') // true

    但是由于定义的原因, Object.prototype.toString.call() 对于自定义类型就心有余而力不足了。所以,对于自定义类型就只能借助上面所提到的 contructor 属性或者 instanceof 运算符来检测了。

  特殊方式

    isNaN():如上所述,专门用来判断 NaN 的数据,用法是 isNaN(variable) ;

    Array.isArray():用于确定传递的值是不是 Array。

// 当检测Array实例时, Array.isArray 优于 instanceof,因为Array.isArray能检测iframes。
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3] // Correctly checking for Array
Array.isArray(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false //在兼容性方面,Array.isArray在IE9-浏览器上没有这个方法,所以我们可以改写下方法以兼容低版本浏览器 if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
} // or
var isArray = function(arr){
return typeof Array.isArray === "function" ? Array.isArray(arr) : Object.prototype.toString.call(arr) === "[object Array]";
}; var arr = [1, 2, 3, 4];
console.log(isArray(arr)); // true
console.log(Array.isArray(arr)); // true

  

三、总结及知识结构图

  由此,简单总结下:

    • Object.prototype.toString.call() :覆盖范围广,特别是对于JS内置数据类型,无论基本类似还是引用类型都适用。缺点是不能检测自定义类型,需要配合用到 instanceof 方法。
    • constructor :通过原型链的继承实现检测,对大部分基本类似和引用类型适用,特别是改进后的 is 函数。但也是原型继承的原因,对于自定义类型的检测不太稳定,可配合 instanceof 方法使用。
    • instanceof :因为是通过原型链检测的,所以仅适用于引用类型的检测。
    • prototype.isPrototypeOf() :  instanceof 操作符的逆向操作,结论与 instanceof 操作符相同,同样仅适用于引用类型的检测。
    • typeof :适用于基本类型的检测,对于 null 的检测结果是 object ,对于函数的检测结果是 function 。
    • isNaN :检测传入值是不是 NaN ,也是该类数据的唯一精准检测方法。
    • Array.isArray :检测传入值是不是 Array ,在跨 iframe 时,使用优先级高于 instanceof 操作符。

四、参考及拓展资料

  JavaScript系列文章:不能不看的数据类型检测

  JavaScript中数据类型转换

  JavaScript检测原始值、引用值、属性

javascript 数据类型 -- 检测的更多相关文章

  1. JavaScript 数据类型检测总结

    JavaScript 数据类型检测总结 原文:https://blog.csdn.net/q3254421/article/details/85483462 在js中,有四种用于检测数据类型的方式,分 ...

  2. JavaScript: 数据类型检测

    由于JavaScript是门松散类型语言,定义变量时没有类型标识信息,并且在运行期可以动态更改其类型,所以一个变量的类型在运行期是不可预测的,因此,数据类型检测在开发当中就成为一个必须要了解和掌握的知 ...

  3. JavaScript数据类型检测 数组(Array)检测方式

    前言 对于确定某个对象是不是数组,一直是数组的一个经典问题.本文专门将该问题择出来,介绍什么才是正确的javascript数组检测方式 typeof 首先,使用最常用的类型检测工具--typeof运算 ...

  4. Javascript数据类型检测

    Javascript有5种简单数据类型和一种复杂数据类型 基本数据类型:String,Boolean,Number,Undefined, Null 引用数据类型:Object(Array,Date,R ...

  5. JavaScript数据类型检测详解

    //JS该如何检测数据的类型呢? //使用关键字: typeof //输出结果依次为:'number','string','boolean'. console.log(typeof 17); cons ...

  6. javascript数据类型检测方法

    一.字符串.数字.布尔值.undefined的最佳选择市使用 typeof 运算符进行检测: 对于字符串,typeof 返回"string" 对于数字,typeof 返回" ...

  7. JavaScript系列文章:不能不看的数据类型检测

    由于JavaScript是门松散类型语言,定义变量时没有类型标识信息,并且在运行期可以动态更改其类型,所以一个变量的类型在运行期是不可预测的,因此,数据类型检测在开发当中就成为一个必须要了解和掌握的知 ...

  8. javascript 中检测数据类型的方法

    typeof 检测数据类型 javascript 中检测数据类型有好几种,其中最简单的一种是 typeof 方式.typeof 方法返回的结果是一个字符串.typeof 的用法如下: typeof v ...

  9. 【JavaScript框架封装】数据类型检测模块功能封装

    数据类型检测封装后的最终模块代码如下: /*数据类型检验*/ xframe.extend(xframe, { // 鸭子类型(duck typing)如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭 ...

随机推荐

  1. 移动开发常用head部分

    <!--[viewport] 设置布局viewport的各种信息: width=device-width:布局viewport宽度等于设备宽度 initial-scale=1.0:默认缩放比为1 ...

  2. 文件的暂存(git add)

    如果我们更改了之前已经被跟踪的main.c,然后执行git status $ git status On branch master Changes not staged for commit: (u ...

  3. hihocoder 1175

    拓扑排序 hihocoder 1175 拓扑只适用于 有向无环图中,这个指的是 1.有向的,不是那种双向可走的 2.无环,并不是不存在环,而是一定要有一个没有其他点指向这个点的点, 题目大意:一个有向 ...

  4. Tomcat使用shutdown.bat关闭会将其他Tomcat关掉的问题

    Tomcat使用shutdown.bat关闭会将其他Tomcat关掉的问题 shutdown.bat文件有一句if not "%CATALINA_HOME%" == "& ...

  5. windows cmd下作MD5校验

    CertUtil -hashfile C:\xxx.tar MD5 此命令不仅可以做MD5哈希算法校验,还支持其他的哈希算法,具体如下: CertUtil -hashfile 文件路径 [算法] 支持 ...

  6. css奇技淫巧-色彩渐变与动态渐变

    来源 css渐变 CSS 中设置的渐变是 gradient 数据类型,它是一种特别的image数据类型.使用background-image设置,可叠加设置多个: CSS3 定义了两种类型的渐变(gr ...

  7. ASP.NET Core 中使用Session会话

    添加Session Nuget包 更新Startup.cs文件 在ConfigureServices方法中添加如下代码 services.AddSession(options => { // S ...

  8. 关于element-ui表单验证(自定义验证规则)

    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-widt ...

  9. CentOS7各个版本镜像下载地址

    # CentOS7.6 下载地址 # CentOS-7-x86_64-DVD-1810.iso CentOS 7.6 DVD 版 4G http://mirrors.163.com/centos/7. ...

  10. Java 关于类的构造方法的一点认识

    2019年4月21日 星期天 在ORACLE官网上提供的The Java™ Tutorials中,有一节课Providing Constructors for Your Classes(为你的类提供构 ...