# 深度分析数据类型检测四种方案的异同

# 引子

JavaScript 的数据类型检测是我们平时开发中经常会遇到的场景,小到基本数据类型大至各种引用数据类型的检测,都是我们需要掌握的知识点。本章会详细讲解 JavaScript 中各种不同数据类型的检测方法以及最后会实现一个数据类型检测的终极方法。

# 数据类型检测1:typeof

1)返回的结果都是字符串
2)字符串中包含了对应的数据类型
"number"/"string"/"boolean"/"undefined"/"symbol"/"object"/"function"
3)【局限性】

(1) typeof null => "object"     null不是对象,它是空对象指针<br>
(2) 检测数组或正则等特殊的对象,返回结果都是'object',所有无法基于typeof判断是数据还是正则<br>
let obj = {}; 

此时obj存储的是堆的16进制地址,obj = AAAFFF000,因为堆内存不会被销毁 如果想让堆内存销毁,应让obj = null obj指向空对象指针 此时应用的是谷歌浏览器v8底层机制(在空闲时间随时监测没有被占用的内存, 如果没有被占用,浏览器内部会手动帮我们销毁)

console. log(typeof []); // => "object"
console. log(typeof typeof []); // => "string"

# 数据类型检测2:instanceof

检测某个实例是否属于这个类
它检测的底层机制: 所有出现在其原型链上的类,检测结果都是true
【局限性】
1、由于可以基于__proto__或者prototype改动原型链的动向
所以基于instanceof检测出来的结果并不一定是准确的
2、基本数据类型的值,不是对象类型,也没有__proto__,虽说也是所属类的实例,在JS中也可以调取所属类原型上的方法,但是instanceof是不认识的,故不能检测基本类型值

局限性
console. log(12 instanceof Number); // => false
new Number(12) instanceof Number; // => true

(12). __proto__; // => Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ,  …}
上面方法相当于浏览器帮我们实现了 new Number(12)
正确用法:
console. log(new Number(12) instanceof Number); // => true
console. log([] instanceof Array); //=>true
console. log([] instanceof Object); //=>true
[] instanceof Array
[] instanceof Object
  dir()查看一个对象的所有属性
  dir([]) => 
   1、先在[]自身的__proto__原型链上查找,找到constructor: Array()
   2、紧接沿着原型链__proto__找到根级[]. __proto__. __proto__(指向object. prototype)

只要在[]的原型链**proto**上检测结果都为true

但是这样导致一个问题,比如原型链指向被更改的问题,比如下面这种情况

Function Fn() {}
Fn. prototype. __proto__ = Array. prototype
let f = new Fn()

f的原型链 Fn -> Array() -> Object

弊端:如果手动更改原型链的指向,instanceof就不能准确判断出是哪种复杂数据类型了

arr. __proto__ = Object. prototype; 
arr instanceof Array; // => false

# 数据类型检测3:constructor

弊端:
1、对象的constructor属性可以随便改 2、对象的__proto__原型链一更改指向,导致constrctor也跟着改

let arr = []; 
Array. prototype. constructor = null; 
console. log(arr. constructor); // => null
arr. constructor === Array; // => false

注:constructor和instanceof类似,都有不足

# 数据类型检测4:Object. prototype. toString. call([value]) / ({}). toString. call([value])

只有Object. prototype上的toString才能检测数据类型
不是用来转换为字符串的,而是返回当前实例所属类的信息
格式: "[object 所属类信息]"
例: "[object **Object/Array/RegExp/Date/Function/Null/Undefined/Number/String/Boolean/Symbol... **]"

# 很多内置类的原型上都有toString, 接下来让我们一起看几个

比如:
Number/String/Boolean/Symbol它们的原型上都有:
toString(转化为字符串)
valueOf(返回它们的原始值)
Array/RegExp/Function等内置类的原型上都有:
toString(转化为字符串)
Object的原型上
toString(返回当前实例所属类的信息)
valueOf(返回原始值: 基本类型值)

let arr = [12, 23]; 
arr. toString(); // => "12, 23"  此时调用的是Array. prototype. toString

同理
let fn = function() {}; 
fn. toString(); // => "function() {}"  调用的是Function. prototype. toString

但Object除外
let obj = {name:'AA'}; 
obj. toString(); // => "[object Object]"

让数组也调Object. prototype. toString
Object. prototype. toString. call([]); 
类似 实现让类数组转化为真数组的方法,调用数组原型上的slice()
Array. prototype. slice. call(arguments); 
另一种写法 -> 很好的避免了typeofinstanceof的尴尬

({}). toString. call(12); // => "[object Number]"
({}). toString. call("12"); // => "[object String]"
({}). toString. call(true); // => "[object Boolean]"
({}). toString. call(null); // => "[object Null]"
({}). toString. call(undefined); // => "[object Undefined]"
({}). toString. call([]); // => "[object Array]"
({}). toString. call(function() {}); // => "[object Function]"

Object. prototype. toString. call()的优势

function Fn() {}; 
Fn. prototype = Array. prototype; 
let f = new Fn(); 

Object. prototype. toString. call(f); // => "[object Object]"
f instanceof Array; // => true

[补]如何查看对象调用的是谁身上的toString()?
先在自身查找有无toString()方法,若没有,沿着__proto__原型链查找,直到找到为止

# 总结

  • 对于基本数据类型的检测采用 typeof
  • 对于引用数据类型(非严禁情况下)采用 instanceof,(严禁情况下)采用Object. prototype. toString. call()

Object. prototype. toString. call() 这种方式基本没有什么局限性,是监测数据类型最准确的方式

# 写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注
  • 在后续文章中,会通过剖析JQ库源码,研究其数据监测的方法