web前端高级JavaScript - JavaScript中四种数据类型检测的底层机制及重写instanceof

typeof 检测数据类型的运算符

  • typeof 返回的结果都是字符串
  • 字符串中包含了对应的数据类型:“number/string/boolean/undefined/symbol/bigint/function/object”
  • 如果是多个typeof连在一起则返回的一定都是"string"
  • typeof 原理:按照计算机底层存储的二进制结果来进行检测的,对象存储的二进制都是以“000”开头的
  • typeof null 返回的结果是"object" 因为null的二进制值是000,但null并不是对象
  • 所有的对象都是以000开头,所以基于typeof检测的结果都是“object”,也就是说typeof无法细分是普通对象还是数组对象

instanceof

  • 严格意义上来说,instanceof并不是用来检测数据类型的,而是用来检测当前实例是否属于这个类;
  • 一般只应用于普通对象、数组对象、正则对象、日期对象等具体细分的。
  • 无法应用到原始值类型值的检测上(基本数据类型如10)
  • xxx instanceof Object即使返回true也不能证明xxx就是普通对象object
// xxx instanceof Object即使返回true也不能证明xxx就是普通对象
let arr = [];
console.log(arr instanceof Array);// true
console.log(arr instanceof Object);//true 不能证明arr是普通对象
console.log(arr instanceof RegExp);//false

//instanceof 无法应用到原始值类型值的检测上
let n = 10 ;
let m = new Number(10);
console.log(n.toFixed(2));//10.00 => n是Number类的实例,只不过它是以字面量方式创造出来的原始类型值(其实在执行n.toFixed(2)时,n这个基本类型值浏览器内部也会把它通过Object(n)转换一下,然后再调用方法,这样它就具备__proto__了)
console.log(m.toFixed(2));//10.00 => 结果一样,m也是Number类的实例,而它是以构造函数的方式创建出来的引用类型值
console.log(n instanceof Number);//false 是因为是基本数据类型,没有__proto__
console.log(m instanceof Number);//true

function Person(){
     }
Person.prototype = Array.prototype;
let p1 = new Person();
console.log(p1 instanceof Array);// true
console.log(p1);//虽然p1可以基于__proto__找到Array.prototype,但是它不具备数组的任何特征(length、索引都没有),所以可以断定这家伙肯定不是一个数组,但是console.log(p1 instanceof Array)却返回了true,是因为它基于原型链找到了Array.prototype
//所以说严格意义上来讲,instanceof并不能用来进行数据类型检测
  • instanceof的检测原理
  • 在Function的原型上有个Symbol.hasInstance方法:Function.prototype[Symbol.hasInstance] = function [Symbol.hasInstance]{[native code]}
  • 而基于“ 实例 instanceof 类”检测的时候,浏览器底层实际上是调用Symbol.hasInstance方法进行检测的,如“类[[Symbol.hasInstance]] (实例)” Array[[Symbol.hasInstance]] (arr)
  • 而在Symbol.hasInstance方法的内部,会根据当前实例的原型链(__proto __)上是否存在这个类的原型(prototype)来检测
    • arr.__ proto__ === Array.prototype => arr instanceof Array : true
    • arr.__ proto__.__ proto__ === Object.prototype => arr instanceof Object : true
let obj = {
     };
let arr = [];
console.log(arr instanceof obj);//Uncaught TypeError: Right-hand side of "instanceof" is not callable.
//报错原因是因为obj是一个对象,它没有Symbol.hasInstance这个方法(只有函数才可以调用Function.prototype的这个方法)

constructor

  • 严格意义上讲,它也不是用来检测数据类型的,原本是用来获取实例的构造函数的。基于这个特点可以充当数据类型检测
  • 这家伙比instanceof会好用一些
  • 但检测结果不准确,因为constructor可以随意被修改
let arr = [];
console.log(arr.constructor === Array);// true 在constructor不被修改的情况下这样就可以区分是数组还是普通对象
console.log(arr.constructor === Object);//false 
console.log(arr.constructor === RegExp);//false

function Person(){
     }
Person.prototype = Array.prototype;
let p1 = new Person();
console.log(p1.constructor === Array);// true 一旦原型被重定向,constructor也被改了,所以检测就不准了

let n = 10 ;
let m = new Number(10);
console.log(n.constructor === Number);//true
console.log(m.constructor === Number);//true

Object.prototype.toString.call

  • 专门用来检测数据类型的(很强大很暴力,基本零瑕疵)
  • Number/String/Boolean/Symbol/BigInt/Function/Array/RegExp/Date/Object…的原型上都有toString方法,但是只有Object.prototype.toString是用来检测数据类型的,其它都是用来转换字符串的。
  • 返回结果:“[object 对象[Symbol.toStringTag] ||对象.构造函数|| Object]”,即如果对象有Symbol.toStringTag属性则返回它的值,如果没有这个属性则找对象的构造函数,如果构造函数也没有则找Object
  • 即使constructor被更改也不受影响,但仅对内置类有效,对自定义类是无效的
let class2type = {
     };
let toString = class2type.toString;//Object.prototype.toString;
toString.call([]);//[object Array]
toString.call(10);//[object Number]
toString.call('s');//[object String]
toString.call(function(){
     });//[object Function]
toString.call({
     });//[object Object]
toString.call(Symbol());//[object Symbol]
toString.call(null);//[object Null]
toString.call(undefined);//[object Undefined]
toString.call(/^$/);//[object RegExp]
toString.call(new Date());//[object Date]
class Person{
     }
let p1 = new Person();
toString.call(p1);// [object Object]

class Person{
     
	get[Symbol.toStringTag](){
     
		return "Person"
	}	
}
let p1 = new Person();
toString.call(p1);// [object Person]

重写instanceof

//两个参数
//obj: 要检测的实例对象(不支持原始值类型)
//constructor: 要检测的类(必须是一个函数类型)
function instance_of(obj, constructor){
     
	if(typeof obj == null || (typeof obj !== 'function' && typeof obj !== 'object')) return false;
	if(typeof constructor !== 'function') throw new TypeError("Uncaught TypeError: Right-hand side of 'instanceof' is not callable.");

	//支持Symbol并且拥有Symbol.hasInstance则以这个处理
	if(typeof Symbol !== 'undefined'){
     
		var hasInstance = constructor[Symbol.hasInstance];
		if(typeof hasInstance === "function"){
     
			return hasIntance.call(constructor, obj);
		}
	}

	//因为IE不支持__proto__,所以我们这里不能用__proto__, 换成等价的Object.getPrototypeOf()方法
	//obj.__proto__ === Object.getPrototypeOf(obj);
	let proto = Object.getPrototypeOf(obj);
	let prototype = constructor.prototype;
	//利用循环,如果当前对象的__proto__中没有constructor则向上级原型链中查找
	while(true){
     
		//如果最后已经找到Object了仍然没有找到,则返回false
		if(proto == null) return false;
		//如果找到了直接返回true
		if(proto === prototype)
			return true;
		//如果没有找到则继续向上查找
		proto = Object.getPrototypeOf(obj);
	}
}

console.log(instance_of([], Array));// true
console.log(instance_of([], object));// true
console.log(instance_of([], RegExp));// false
console.log(instance_of(10, Number));// false
console.log(instance_of(new Number(10), Number));// true
console.log(instance_of([], {
     });// 报错, {}不是一个function

你可能感兴趣的:(WEB前端高级教程,数据类型检测,typeof,instanceof,constructor,prototype.toStr)