数据类型检测
typeof用来检测数据类型的运算符
typeof[value]=>字符串,包含对应的数据类型
- 局限性:
- typeof null ->‘object’(null)
- typeof检测对象类型,除了可执行对象「函数」可以检测出来是“function”,(因为里面有call)其余都是“object”(不能细分对象)
- 基于typeof检测一个未被声明的变量,结果是“undefined”,而不会报错「基于let在后面声明则会报错」
- typeof 检测原始值对应的对象类型的值,结果是“object”(不是我们想要的Number)
- 原理 所有数据类型在计算机底层都是按照二进制的值进行存储的,而typeof就是按照二进制的值进行检测的
-
- 性能好
- 对象的二进制值开头都是“000”,而null的二进制都是零,所以typeof检测null的时候,识别其实是对象(这样是不对的);如果识别是对象,在看对象是否实现了call方法,实现了call方法就返回“function”,没实现的一律返回“object”
- 应用:
-
-
检测除null之外的原始值类型可以使用它
-
检测是否为对象 if(obj==null||/^(object|function)$/g.test(typeof obj))
-
检测某个东西是否兼容if(typeof Symbol!=='undefined')
-
instanceof 临时用来“拉壮丁”的检测数据类型,本意是检测当前实例是否属于这个类
[value] instanceof Ctor=>true/false
- 优势细分对象数据类型值「但是不能因为结果是true就说它是标准普通对象」
let arr=[10,20,30,40] //arr.__proto__=>Array.prototype=>Object.prototype
console.log(arr instanceof Array);//true
console.log(arr instanceof Object);//true
- 弊端:不能检测原始值类型的值「但是原始值对应的对象格式实例则可以检测」
console.log(new Number(1) instanceof Number);//true
console.log(1 instanceof Number);//false
- 原理:按照原型链检测的;只要当前检测的构造函数(它的原型对象),出现在实例的原型链上,则检测结果就是TRUE;如果Onject.prototype都没有找到结果就是FALSE
function Fn(){}
Fn.prototype=Array.prototype
let f=new Fn//指向数组原型,没有length和索引的哪些东西
console.log(f instanceof Array);//true
- f instanceof Fn ->FnSymbol.hasInstance
- 底层如何检测:
- 当用instanceof的时候首先会检查构造函数Fn有没有Symbol.hasInstance()这个属性,如果有这个属性,FnSymbol.hasInstance是个函数会把实例f传进去,所以f instanceof Fn ===FnSymbol.hasInstance//true
- Function的原型上会有这个属性:
function Fn(){}
let f=new Fn;
console.log(f instanceof Fn);//true
console.log(Fn[Symbol.hasInstance](f));//true
console.log(f instanceof Fn===Fn[Symbol.hasInstance](f));//true
- 用instanceof 的时候会默认这么会调(前提浏览器要有这个属性,一般都有)
- Fn[Symbol.hasInstance]=function(){}//普通写法的构造函数,它的Symbol.hasInstance属性无法被直接修改;下图中可以看出来Symbol.hasInstance还是函数本身的而不是自己添加的
- 但是Es6语法中可以修改,因此也会出现一些问题
class Fn{
static[Symbol.hasInstance](){
console.log('OK');
return true
}
}
let f=new Fn;
console.log(f instanceof Fn);//true(原因是上边return true,若前面没有return true 则结果是false,由此也可以看出ES6的写法可以修改Symbol.hasInstance属性的)
- 神奇的事情发生了,如果知道了这个机制,我们则可以改写检测的结果!
class Fn{
static[Symbol.hasInstance](obj){
if(Array.isArray(obj))
return true
return false
}
}
let f=new Fn;
let arr=[10,20,30,40]
console.log(arr instanceof Fn);//true
console.log(f instanceof Fn);//false
+ arr instanceof Fn 也会默认调用Symbol.hasInstance这个方法把arr传进来 arr和fn没有关系但是结果是true
-
总结:底层处理的时候每次用instanceof,底层第一件事情先去调用它的Symbol.hasInstance,如果有则按照这个来,把要检测的实例传递进来,而这个方法它的返回值是啥,它检测的结果就是啥,这个方法如果自己不写则按照Function原型上的方法执行,默认也是按照原型链的查找机制去找,看看当前构造函数的原型有没有出现在实例的原型链上,如果有结果是true 没有结果是false,但是知道了这个机制就可以肆无忌惮的来改了
-
面试题:重写instanceof
- 原有的 instanceof 检测左测时实例,右侧必须是个对象,而且是个函数对象(必须有prototype)
- Object.getPrototypeOf(原始值类型) 默认会有装箱的操作,所以这里就没有继续做判断,也可以自己做
- 所以箭头函数不行,右边是对象但是不是函数对象也不行
// 我们这个方法支持原始值类型
const instance_of = function instance_of(obj, Ctor) {
//obj 要检测的实例
//Ctor 要检测的构造函数
// 获取obj的原型链一直到Object
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 undefined 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 Object.getPrototypeOf(1) 默认会有一个装箱的操作
console.log(instance_of(Symbol(), Symbol)); //true
console.log(instance_of([], 1)); //报错
console.log(instance_of([], {})); //报错
let f=()=>{}
console.log(instance_of([],f));//报错
constructor 也是拉“壮丁”
- constructor本身就是存构造函数本身,没有其它意义,但是也可以检测,可以弥补instance of 的一些不足
- 但是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
- 不能因为结果是false就说它不是对象
function Fn() {}
let f = new Fn();
console.log(f.constructor===Fn);//true
console.log(f.constructor===Object);//false
- 因为fn的原型重定向会丢失consructor
function Fn() {}
Fn.prototype={}
let f = new Fn();
console.log(f.constructor===Fn);//false
console.log(f.constructor===Object);//true
+ 重定向的原型里没有了constructor,会根据原型链继续查找则会找到Object
- 因此不能说它的constructor是Object就说它是纯粹对象,在重定向原型这里它是实例对象
Object.prototype.to String.call([value]) 这是JS中唯一一个检测数据类型没有任何瑕疵的,最准确的,除了性能比typeof 略微少一点,写起来麻烦一点
- 它的检测结果是固定的格式
- "[Object Number/String/Boolen/Null/Undefined/Symbol/BigInt/RegExp/Date/Math/Error...]"
- 可以解决typeof 检测原始值对象类型值的缺陷
- 大部分内置类的原型上都有to String方法 一般都是用来转换字符串的,但是Object原型上的是检测数据类型的返回中包含自己所属的构造函数信息
- 大部分内置类的原型上都有toString方法,一般都是转换为字符串的但是Object.prototype.to String是检测数据类型的,返回中包含自己所属的构造函数信息...
Object.prototype.to String.call(value)
- 为啥要用call?
- ([]).toString() 调用的是Array.prototype上的toString,是转换为字符串 +({}).toString() 调用的是Object.prototype上的toString执行,而且还要改变其中的this等价于->({}).toString.call()
- 检测返回值遵循啥规则?
- 一般都是返回当前实例所属的构造函数信息
- 但是如果实例对象拥有Symbol.toStringTag属性,属性值是啥,最后返回的就是啥,例如:Math[Symbol.toStringTag]="Math"=>Object.prototype.to String.call(Math) =>"[object Math]"
- Math本身是Object的实例(但是有了Symbol.toStringTag这个属性)
- 面试题
- 如何将检测f的结果改为[object Fn]
class Fn {}
let f = new Fn();
console.log(Object.prototype.toString.call(Fn));//[object Function]
console.log(Object.prototype.toString.call(f));//[object Object]
- 因为有了Symbol.toStringTag就很轻松解决了这个面试题
class 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]