24. 深度分析JS中检测数据类型的四种方法
No.1 typeof
typeof[value]
=> 检测结果是字符串,包含对应的数据类型- 局限性:
typeof null
->object
- 为啥?typeof检测数据类型是,按照计算机底层存储的二进制值,来进行检测的,它认为以000开始的都是对象,而null全是0
typeof
检测对象类型值,除了可执行对象(function/class)可以检测出来是function
,其余都是object
typeof
检测原始值对应的对象类型的值,结果是object
- 优点 简单、性能好
No.2 instanceof
-
[value] instanceof [Ctor] => true/false
本意是检测当前实例是否属于这个类- 局限性:不能检测原始值类型的值[但是原始值对应对的对象格式的实例则可以检测],但是因为原型链指向是可以肆意改动的,所以最后检测的结果不一定准确
- 优点:相比于
typeof
可以细分对象数据类型值[但不能因为结果是true
,就说他是标准普通对象] - 原理 对于上面的介绍,大家可能有些疑问,下面将介绍instanceof的原理,并且我们将自己实现一个instanceof,看完原理解析后再返回来看,相信会有收获的。
//用ES5的方式创建一个构造函数 function Fn(){ } let f = new Fn console.log(f instanceof Fn) // true
对于上面的结果相信大家没有疑问,那他的原理是什么呢,看下面这张图
在我们使用
f instanceof Fn
的时候浏览器会去调用我们要检测的所属构造函数(Fn)的[Symbol.hasInstance]属性,这个属性的值是一个函数,函数的返回值决定了我们的检测结果,ES5的构造函数写法,他的Symbol.hansInstance属性是无法被直接修改,但是在ES6语法中可以被修改。注:[Symbol.hasInstance]这个属性在
Fn.__proto__
上即Function.prototype
上class Fn { static[Symbol.hasInstance](obj) { console.log('Symbol.hasInstance 执行'); if (Array.isArray(obj)) return true; return false; } } let f = new Fn; let arr = [1, 2, 3]; console.log(f instanceof Fn); //=>false console.log(arr instanceof Fn); //=>true
在浏览器控制台执行这段代码,我们可以看出再我们使用
instanceof
检测的时候浏览器去调用了Fn[Symbol.hasInstance]方法,而且输出的结果并不是我们预期的结果,因为可以被改动,所以检测的结果并不一定是准确的。- 实现自己的instanceof
function Fn() {} Fn.prototype = Array.prototype; let f = new Fn; console.log(f instanceof Array);//=>true //印证了上面所说的instanceof的局限性 let arr = [10, 20, 30]; //arr.__proto__ -> Array.prototype -> Object.prototype console.log(arr instanceof Array); //=>true console.log(arr instanceof RegExp); //=>false console.log(arr instanceof Object); //=>true console.log(new Number(1) instanceof Number); //=>true console.log(1 instanceof Number); //=>false /* 从上面的输出我们大概推测出了[Symbol.hasInstance]方法检测的原理:按照原型链检测的;只要当前检测的构造函数的原型对象,出现在实例的原型链上,则检测结果就是TRUE;如果找到Object.prototype都没有找到,则结果是FALSE; */ //我们这个方法支持原始值类型检测 const my_instanceof = function my_instanceof(obj, Ctor){ if(Ctor == null) throw new TypeError('Right-hand side of instanceof is not an object') let type = typeof Ctor if(!/^(object|function)$/i.test(type)) throw new TypeError('Right-hand side of instanceof is not an object') if (!/^function$/i.test(type)) throw new TypeError('Right-hand side of instanceof is not callable') if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check') let proto = Object.getPrototypeOf(obj) while(proto){ if(proto === Ctor.prototype) return true; proto = Object.getPrototypeOf(proto) } return false } console.log(instance_of([], Array)); //=>true console.log(instance_of([], RegExp)); //=>false console.log(instance_of(1, Number)); //=>true console.log(instance_of(Symbol(), Symbol)); //=>true console.log(instance_of([], () => {})); //报错 Function has non-object prototype in instanceof check
No.3 constructor
constructor
的修改比instanceof
更'肆无忌惮'
let arr = [],
n = 10,
m = new Number(10);
console.log(arr.constructor === Array); //=>true
console.log(arr.constructor === RegExp); //=>false
console.log(arr.constructor === Object); //=>false
// 如果CTOR结果和Object相等,说明当前可能是标准普通对象
console.log(n.constructor === Number); //=>true
console.log(m.constructor === Number); //=>true
function Fn() {}
let f = new Fn;
console.log(f.constructor === Fn); //=>true
console.log(f.constructor === Object); //=>false
function Fn() {}
Fn.prototype = {}; //重定向后丢失了constructor
let f = new Fn;
console.log(f.constructor === Fn); //=>false
console.log(f.constructor === Object); //=>true
No.4 Object.prototype.toString.call([value])
- 这是JS中唯一一个检测数据类型没有任何瑕疵的「最准确的,除了性能比typeof略微少那么一丢丢,写起来略微麻烦那么一丢丢」
Object.prototype.toString.call([value]) => "[object Number/String/Boolen/Null/Undefined/Symbol/BigInt/Object/Function/Array/RegExp/Date/Math/Error...]"大部分内置类的原型上都有toString方法,一般都是转换为字符串的,但是Object.prototype.toString是检测数据类型的,返回值中包含自己所属的构造函数信息...
- 1.为啥要用call?
- ([]).toString() 调用的是Array.prototype上的toString,是转换为字符串
- ({}).toString() 调用的是Object.prototype上的toString,是检测数据类型的「方法中的this是谁,就是检测谁的类型」 -> 我们需要把Object.prototype.toString执行,而且还要改变其中的this -> ({}).toString.call(10) =>“[object Number]”
- 2.检测返回值遵循啥规则?
- 一般都是返回当前实例所属的构造函数信息,但是如果实例对象拥有 Symbol.toStringTag 属性,属性值是啥,最后返回的就是啥,例如:
Math[Symbol.toStringTag]="Math" => Object.prototype.toString.call(Math) “[object Math]”
class Fn { // constructor() { // this[Symbol.toStringTag] = 'Fn'; // } [Symbol.toStringTag] = 'Fn'; } let f = new Fn; // console.log(Object.prototype.toString.call(Fn)); //[object Function] console.log(Object.prototype.toString.call(f)); //[object Fn]
- 一般都是返回当前实例所属的构造函数信息,但是如果实例对象拥有 Symbol.toStringTag 属性,属性值是啥,最后返回的就是啥,例如:
- 3.自己封装一个检测数据类型的方法
const checkType = function checkType(obj) {
if (obj == null) return obj + ""; //判断null和undefined
const reg = /^\[object ([a-zA-Z0-9]+)\]$/i;
return typeof obj === "object" || typeof obj === "function" ?
reg.exec(Object.prototype.toString.call(obj))[1].toLowerCase() :
typeof obj;
};