JS中数据类型检测的办法分类:
typeof [value][实例] instanceof [构造函数][对象].constructor===[构造函数]Object.prototype.toString.call([value])
其他小方法:
Array.isArray([value]):检测[value]是否是数组isNaN([value]):检测[value]是否为NaN
typeof
返回一个字符串,字符串中包含了对应的数据类型。 所以 typeof typeof ... 一定是返回 "string"
- 根据计算机底层存储的“二进制值”来检测的(性能会好一些)
- 原始值数据类型(不含
null)以及函数值的检测,基于typeof处理比较准确
优点:
typeof 10 //'number'
typeof '' //'string'
typeof true //'boolean'
typeof undefined //"undefined"
typeof Symbol('xxx') //"symbol"
typeof 123n//"bigint"
注意:局限性
typeof null->"object"typeof不能细分对象typeof new Number(10)->"object"不能检测将原始值经过包装的数据类型
typeof null//'object'
typeof []//'object'
typeof /^/
//正则 'object'
typeof{}//'object'
typeof function(){}//'function'
typeof function*(){}//'function'
typeof Number(10)//'object'
instanceof
[实例] instanceof [构造函数]
本意:不是检测数据类型,而是检测当前实例是否属于这个类(是不是这个类的实例),用来检测数据类型仅是一个延伸的用法,所以存在很多弊端。但是可以基于 instanceof 细分对象类型
console.log([] instanceof Array); //->true
console.log([] instanceof RegExp); //->false
console.log([] instanceof Object); //->true
1 instanceof Number//false
new Number(1) instanceof Number//true
注意:为什么 [] instanceof Object 也是 true ?
instanceof 原理:首先按照 [构造函数][Symbol.hasInstance]([实例]) ,如果存在这个属性方法,则方法执行返回的值就是最后检测的结果;如果不存在这个属性方法,则会基于当前 [实例] 的 __proto__ 一直往上找。如果查找中途,找到的某个原型 __proto__ 等于 [构造函数].prototype ,即构造函数的原型出现在其原型链上,则返回结果是 true ,反之 false 。(顺着原型链一直往上找,一直找到 Object.prototype 为止)
注意(局限性):
- 因为原型可以被重定向,所以检测的结果不一定准确
- 原始值类型使用
instanceof是无法检测的
function Fn() {}
Fn.prototype = [];
let f = new Fn;
// 正常理解:f肯定不是数组
console.log(f instanceof Array); //->true
console.log(f instanceof Object); //->true
console.log((1) instanceof Number); //->false 这样处理不会转换
console.log((1).toFixed(2)); //->'1.00' 这样可以操作是因为浏览器默认会有一个有一个把1转换为对象格式1的操作"Object(1)" 装箱操作
所以检测原始值类型也是按照其原理来的
native code 指的是内置的代码
基于es5代码无法覆盖 [构造函数][Symbol.hasInstance] 方法,但是基于es6是可以重写的,以下代码,我们使用任何值检测 Fn ,都是 true
class Fn {
// 基于ES6设置静态私有属性是有效的
static[Symbol.hasInstance](value) {
console.log(value);
return true;
}
}
console.log([] instanceof Fn);//true
重写 instanceof
需要考虑的边界问题
- 传入的构造函数不能是
null和undefined - 因为有一段逻辑需要使用原型判断,所以传入的构造函数必须要有
prototype。并不是所有的函数都有原型,箭头函数就没有原型,对象也没有原型 - 必须是函数
function instance_of(obj, Ctor) {
// 数据格式准确性校验//传入的构造函数不能是 ` undefined ` 或者 ` null `
if (Ctor == null) throw new TypeError("Right-hand side of 'instanceof' is not an object");
//构造函数必须要有 ` prototype ` ,并不是所有的函数都有原型,箭头函数就没有原型
if (!Ctor.prototype) throw new TypeError("Function has non-object prototype 'undefined' in instanceof check");
//构造函数必须是函数
if (typeof Ctor !== "function") throw new TypeError("Right-hand side of 'instanceof' is not callable");
// 原始类型直接忽略
if (obj == null) return false;
if (!/^(object|function)$/.test(typeof obj)) return false;
// 先检测是否有 Symbol.hasInstance 这个属性
if (typeof Ctor[Symbol.hasInstance] === "function") return Ctor[Symbol.hasInstance](obj);
// 最后才会按照原型链进行处理
let prototype = Object.getPrototypeOf(obj);//获取原型
while (prototype) {//直到没有原型为止
if (prototype === Ctor.prototype) return true;//直到找到位置
prototype = Object.getPrototypeOf(prototype);//继续往上获取
}
return false;
}
instance_of([12, 23], Array) //=>true
instance_of(1, Number) //=>false
instance_of(null, Number) //=>false
instance_of(new Number(1), Number) //=>true
instance_of([], Object) //=>true
instance_of([], RegExp) //=>false
总结:
- 优点:可以基于
instanceof细分对象类型 - 局限性:
- 无法检测原始值类型
- 不准确,因为原型可以被重定向
[对象].constructor===[构造函数]
本意:获取对象的构造函数,用来检测数据类型仅是一个延伸的用法,所以存在弊端, constructor 是可以允许被肆意更改的,检测结果是不准确的
注意区别 instanceof 。这种方法并没有依据原型链继续往上寻找,并且它可以处理原始值类型(装箱操作)(除了 null 和 undefined 这两个原始值,这两个原始值不允许成员访问)
let value = [];
console.log(value.constructor === Array); //->true
console.log(value.constructor === RegExp); //->false
console.log(value.constructor === Object); //->false
value = 1;
console.log(value.constructor === Number); //->true
Object.prototype.toString.call([value])
推荐方案:除了写起来麻烦一些,基本没有弊端
以下来源于MDN:
每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
默认情况下, toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖, toString() 返回 "[object type]",其中 type 是对象的类型。
原理:首先找到 Object.prototype.toString 方法,把 toString 执行的之后,让方法中的 this 变为要检测的这个值, toString 内部会返回对应 this (也就是要检测这个值)的数据类型信息 "[object ?]"
补充:
-
大部分值(实例)所属类的原型上都有
toString方法。除了Object.prototype.toString(Object原型上的这个最原始的toString)是用来检测数据类型的,其余的一般都是用来转换为字符串的,因为他们在继承时都被重写了。非普通对象.toString:调取的都是自己所属类原型上的toString,并不是Object.prototype.toString,基本都是转换为字符串普通对象.toString:调取Object.prototype.toString这个方法,所以是检测数据类型console.log(({}).toString()); //->"[object Object]" console.log(Object.prototype.toString.call({})); //->"[object Object]" -
"[object ?]"中?是什么,内部原理是:首先看[value][Symbol.toStringTag]属性 ,如果存在这个属性,属性值是什么?就是什么。如果没有这个属性,一般是返回所属的构造函数信息。let fn = function* () {}; console.log(Object.prototype.toString.call(fn)); //->"[object GeneratorFunction]" fn[Symbol.toStringTag] //"GeneratorFunction"
- 返回值包括
"[object Number/String/Boolean/Null/Undefined/Symbol/BigInt/Object/Array/RegExp/Date/Error/Function/GeneratorFunction/Math...]"注意const class2type = {}, toString = class2type.toString; //->Object.prototype.toString console.log(toString.call(1))//[object Number] console.log(toString.call(Infinity))//[object Number] console.log(toString.call(''))//[object String] console.log(toString.call(true))//[object Boolean] console.log(toString.call(null))//[object Null] console.log(toString.call(undefined))//[object Undefined] console.log(toString.call(Symbol()))//[object Symbol] console.log(toString.call(10n))//[object BigInt] console.log(toString.call({}))//[object Object] console.log(toString.call([]))//[object Array] console.log(toString.call(/^/))// [object RegExp] console.log(toString.call(new Date))//[object Date] console.log(toString.call(function(){}))//[object Function] console.log(toString.call(()=>{}))//[object Function] console.log(toString.call(function* () {}))//[object GeneratorFunction] console.log(toString.call(Math))//[object Math]Math是一个对象,里面有一些方法可以使用,Date是构造函数
面试题:
function Fn() {
this.x = 100;
}
Fn.prototype = {
constructor: Fn,
getX() {
console.log(x);
}
};
let f = new Fn;
console.log(Object.prototype.toString.call(f)); // 默认是“[object Object]”
在获取 f[Symbol.toStringTag] 时,一直找到最后都没这个属性,所以浏览器默认是 'Object'
那么如何返回 "[object Fn]" ?
依据原理去解决即可
function Fn() {
this.x = 100;
}
Fn.prototype = {
constructor: Fn,
getX() {
console.log(x);
},
// 这样所有Fn的实例(f)就拥有这个属性,后期基于toString检测类型,返回“[object Fn]”
[Symbol.toStringTag]: 'Fn'
};
let f = new Fn;
console.log(Object.prototype.toString.call(f)); // "[object Fn]"