阐明
本文所介绍的一切知识点、代码示例以及供给的解决方案,均不考虑 IE
浏览器,仅支撑最新版别的 Chrome
、Firefox
、Edge
和 Safari
浏览器。
概述
前端开发过程中一个常见的功用是:检测某个数据归于什么类型,是字符串、数字、数组、还是目标等等。比如,咱们定义了一个函数,而且支撑传参,往往就需求对传入的参数进行数据类型检测,然后依据检测成果进行相应的处理,这时咱们就有必要知道如何精确的获取数据的类型。在构思解决方案之前,咱们首要需求回忆一下基础知识,那便是在 JavaScript
中到底有几种数据类型?
数据类型品种
这儿所讲的数据类型指的是 JavaScript
言语层面的数据类型,截至现在,共有 8
品种型,可分为【底子数据类型】和【引证数据类型】:
底子数据类型
- 字符串(
String
) - 数字(
Number
) - 布尔值 (
Boolean
) null
undefined
Symbol
BigInt
引证数据类型
- 目标(
Object
,Array
等等 )
区别
上面说到的【底子数据类型】和【引证数据类型】有什么区别呢?
底子数据类型的值是保存在 “栈” 内存中的,它是能够直接拜访的,一切的读写操作都是直接效果于数据本身,中心没有任何 “转接” 行为。
引证数据类型的值是保存在 “堆” 内存中的,在 JavaScript
中是不允许直接拜访堆内存中的数据的,要想拜访就需求拿到它在堆内存中的地址,然后经过这个地址进行读写操作。
举个例子:张三要跟李四交流事情,底子数据类型就相当于,张三直接跟李四本人交流。而引证数据类型则相当于张三要跟 “代理人” 交流,再由这个 “代理人” 把张三的需求转述给李四,李四如有反应,也有必要经过 “代理人” 转告给张三,张三和李四由始至终都不能直接交流。
检测办法
typeof
运算符
这是最简单也是最常用的数据类型检测办法,但一起它也不太 “靠谱”,为什么这样说呢?能够先看看下面的代码示例:
console.log( typeof "data" ); // string
console.log( typeof 123456 ); // number
console.log( typeof true ); // boolean
console.log( typeof function () {} ); // function
console.log( typeof Symbol() ); // symbol
console.log( typeof 100n ); // bigint
console.log( typeof undefined ); // undefined
console.log( "===================================" );
console.log( typeof null ); // object
console.log( typeof { a: "a" } ); // object
console.log( typeof [ 1, 2, 3 ] ); // object
能够看到,关于前七种数据,能检测出相应的类型,而后三种却一概回来 object
。前面曾说到,Array
和 Object
都归于引证数据类型,而 null
被认为是对空目标的引证,也归归于 Object
范畴,由此可见,typeof
是无法区分出引证数据类型的。
上面的示例中还有一个要害点,那便是 function
函数。函数实践上也是目标,它并不代表一种数据类型,但它却十分特别。函数具有目标的一切才能,但一起它本身还具有特别的属性,而且与目标相比,函数还有一个特别之处,便是它是可调用的,你能够手动调用函数去执行某个操作。根据以上特别情况,在 ECMAScript 标准中规则了能够经过 typeof
区分出函数和其它目标。
除了上述能检测出的七品种型之外,简直其它一切类型经 typeof
检测后都是回来 object
,例如:
console.log( typeof document.children ); // object
console.log( typeof window ); // object
console.log( typeof document.querySelector( "html" ) ); // object
console.log( typeof document.createElement( "div" ) ); // object
console.log( typeof new Map() ); // object
console.log( typeof new Set() ); // object
console.log( typeof new Promise( () => {} ) ); // object
至此,能够得到一个初步结论,运用 typeof
运算符只能检测出:字符串、数字、布尔值、函数、Symbol
、BigInt
和 undefined
七品种型,关于数组、目标、null
和其它类型则无能为力,需求另寻他法。
这儿还需求阐明一个特别情况,关于字符串、数字、布尔值这三种底子数据类型,还存在对应的特别引证类型:
new String()
new Number()
new Boolean()
console.log( ( new String( "aa" ) ).valueOf() === "aa" ); // true
console.log( ( new Number( 1234 ) ).valueOf() === 1234 ); // true
console.log( ( new Boolean( true ) ).valueOf() === true ); // true
因而,一旦经过上述的办法创立字符串、数字或许布尔值,运用 typeof
将无法得到精确的类型:
console.log( typeof new String( "aa" ) ); // object
console.log( typeof new Number( 1234 ) ); // object
console.log( typeof new Boolean( true ) ); // object
由此可见,typeof
运算符关于字符串、数字和布尔值的类型判定,无法做到百分百的必定精准。不过,在实践开发中,底子上很少会遇到运用上述特别办法创立这三种数据类型的情况。因而,依然能够继续运用 typeof
进行判别。
instanceof
运算符
以下是 MDN 关于 instanceof
的描绘:
instanceof运算符用于检测结构函数的
prototype
属性是否呈现在某个实例目标的原型链上。
语法:obj instanceof constructor
由于 instanceof
是根据 ”原型“ 的,因而它只适用于检测引证数据类型,如:目标、数组等。
咱们先来看一下示例:
const obj = {
a: "a"
};
console.log( obj instanceof Object ); // true
console.log( Object.getPrototypeOf( obj ) === Object.prototype ); // true
在上面的示例中,obj
是一个经过字面量办法创立的目标,本质上相当于 new Object()
,也便是说,obj
是由 Object()
结构函数构建出来的,那么 obj
的原型链上必定包括 Object
的原型。
再看一个数组的例子:
const arr = [ 1, 2, 3 ];
console.log( arr instanceof Array ); // true
同样的原理,arr
是一个经过字面量办法创立的数组,本质上相当于 new Array()
,那 arr
的原型链上也必定包括 Array
的原型,因而,上面的逻辑是没问题的,可是如果对代码稍加改造,将 Array
换成 Object
会是什么成果呢?
const arr = [ 1, 2, 3 ];
console.log( arr instanceof Object ); // true
成果显现也为 true
,这是由于在 JavaScript
中,数组其实也是目标,不仅仅是数组,凡是经过 new
要害字创立的实例本质上都是目标。所以,前文说到的 typeof new xxx
的成果都是 object
。也正因如此,数组的原型链中也必定包括 Object
的原型。
别的需求阐明的是,instanceof
在多 iframe
环境下会存在问题,由于这意味着存在多个全局环境,而不同的全局环境具有不同的全局目标,然后具有不同的内置类型结构函数,这将会导致 instanceof
呈现紊乱。
Object.prototype.toString.call()
这种绝妙的检测办法最早是由 ”始祖级“ 的 JavaScript
类库 Prototype.js
发掘出来的。这简直要追溯到近 20 年前了,那时的前端还处在萌发时期,各种标准标准没有完善,还要面对令人抓狂的浏览器兼容问题,因而要想精确检测出各种数据类型简直是难如登天。各大程序库想尽了办法,各种奇技淫巧层出不穷,直到这种办法的呈现,总算有了一个安稳的检测办法,之后的库和框架也底子都是用此办法来检测数据类型。
它的底子原理实践上便是输出目标内部的类属性 [[Class]]
的值,这在绝大多数情况下是必定精确的。这儿先看第一个知识点:toString
。
简单来说,toString
办法便是将目标以字符串的办法回来。JavaScript
中简直一切目标都有 toString
办法,null
和 undefined
没有 toString
办法,下面经过代码示例看一下每品种型调用 toString
后回来的成果:
console.log( ( new String( "a" ) ).toString() ); // a
console.log( ( new Number( 100 ) ).toString() ); // 100
console.log( ( new Boolean( true ) ).toString() ); // true
console.log( [ 1,2,3 ].toString() ); // 1,2,3
console.log( { a: "a" }.toString() ); // [object Object]
console.log( Symbol().toString() ); // Symbol()
console.log( 100n.toString() ); // 100
上述成果能够看出,每个目标的 toString
办法都有自己的一套逻辑, 因而输出的成果不尽相同,而且上面的成果也阐明了,单纯运用各自的 toString
办法得到的值也没能表示出相关类型,只有一个 [object Object]
值得研究。
为什么会得到 [object Object]
呢?这是由于目标的 toString
办法无法将目标正确解析为字符串,所以 JavaScript
引擎直接回来了字符串 [object Object]
。此时咱们能够做出一个这样的假设:由于是在 object
类型的数据上调用了 toString
办法,回来了 [object Object]
,而这个字符串中的两个单词都是 object
(先不考虑大小写),能否阐明这个字符串实践现已包括了类型信息呢?如果这个假设建立,那么理论上其它类型的数据应该也能够经过这种办法获取到类型。可是前面说到了,每个目标的 toString
办法都有自己的一套逻辑,回来的内容五花八门,现在就需求想办法让它们也能回来相似 [object Object]
这种办法的字符串,以此来揣度其所属类型。这儿就需求用到原型属性,由于一切的目标都继承自 Object
,既然它们各自的 toString
办法有自己的逻辑,那咱们就不必他们本身的 toString
,而是运用继承自 Object
原型上的 toString
, 也便是 Object.prototype.toString
,那为什么后边还用了一个 call
呢? 先来看一下不必 call
的成果:
console.log( Object.prototype.toString( [] ) ); // [object Object]
console.log( Object.prototype.toString( {} ) ); // [object Object]
console.log( Object.prototype.toString( "aa" ) ); // [object Object]
console.log( Object.prototype.toString( 11 ) ); // [object Object]
单纯运用 Object.prototype.toString
将一概回来 [object Object]
,由于这始终是在调用 Object
的 toString
办法,其内部的 this
始终指向的是 Object
,所以就有必要要借助 call
改动 this
的指向( apply
也能够 ), 所以才有了 Object.prototype.toString.call()
的写法。其实能够这样理解:我自己的 toString
被我重写了,不能用了,那我就用 Object
的 toString
,由于它是原始纯净的,能回来我想要的东西,而且我继承自 Object
,能借用它的一切,自然也就能借用它的 toString
,只需在借用时注明是我在运用就能够了( call
的效果 )。
下面就看看运用 Object.prototype.toString.call()
到底能否回来咱们想要的成果吧。
console.log( Object.prototype.toString.call( "aa" ) ); // [object String]
console.log( Object.prototype.toString.call( 1000 ) ); // [object Number]
console.log( Object.prototype.toString.call( true ) ); // [object Boolean]
console.log( Object.prototype.toString.call( 100n ) ); // [object BigInt]
console.log( Object.prototype.toString.call( null ) ); // [object Null]
console.log( Object.prototype.toString.call( undefined ) ); // [object Undefined]
console.log( Object.prototype.toString.call( Symbol() ) ); // [object Symbol]
console.log( Object.prototype.toString.call( [ 1,2,3 ] ) ); // [object Array]
console.log( Object.prototype.toString.call( { a: "a" } ) ); // [object Object]
console.log( Object.prototype.toString.call( function () {} ) ); // [object Function]
再看看其它类型的数据
// [object Promise]
console.log( Object.prototype.toString.call( new Promise( () => {} ) ) );
// [object HTMLHtmlElement]
console.log( Object.prototype.toString.call( document.querySelector( "html" ) ) );
// [object HTMLDivElement]
console.log( Object.prototype.toString.call( document.createElement( "div" ) ) );
// [object HTMLCollection]
console.log( Object.prototype.toString.call( document.children ) );
// [object HTMLDocument]
console.log( Object.prototype.toString.call( document ) );
// [object Window]
console.log( Object.prototype.toString.call( window ) );
// [object Set]
console.log( Object.prototype.toString.call( new Set() ) );
// [object Map]
console.log( Object.prototype.toString.call( new Map() ) );
依据以上成果能够得知,回来成果都是以 [object
最初,以 类型]
结束,那么咱们加工一下就能够用它直接回来类型了:
Object.prototype.toString.call( obj ).slice( 8, -1 );
那这个办法真的必定稳妥吗?99%
的情况下是稳妥的,但不排除极特别情况,比如:
Object.prototype.toString = () => "哈哈哈";
console.log( Object.prototype.toString.call( "aa" ) ); // 哈哈哈
console.log( Object.prototype.toString.call( 1000 ) ); // 哈哈哈
console.log( Object.prototype.toString.call( true ) ); // 哈哈哈
console.log( Object.prototype.toString.call( 100n ) ); // 哈哈哈
由此可见,如果最原始的 Object.prototype.toString
被改写了,那么这个办法就失效了,不过正常情况下谁会这样做呢?
封装示例
根据以上各种检测手法,咱们能够封装一个底子的类型检测办法,下面是一个最底子的封装示例,我们能够自行完善。
function getType ( data ) {
// 关于简单的类型直接运用 typeof 判别
let type = "";
switch ( typeof data ) {
case "string": type === "string"; break;
case "number": type === "number"; break;
case "boolean": type === "boolean"; break;
case "function": type === "function"; break;
case "symbol": type === "symbol"; break;
case "bigint": type === "bigint"; break;
case "undefined": type === "undefined"; break;
}
if ( type ) {
return type;
}
// 数组类型直接运用原生供给的 Array.isArray
if ( Array.isArray( data ) ) {
return "array";
}
// 其他类型运用 Object.prototype.toString.call 获取
return Object.prototype.toString.call( data ).slice( 8, -1 ).toLowerCase();
}