数据类型检测的方式有哪些
1.typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
其中,数组,对象,null都会被判断为Object,其他的都判断正确
2.instanceof
instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中是否找到该类型的原型
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
可以看到,instanceof只能正确的判断引用数据类型,而不能判断基本数据类型。instanceof可以用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性
注意:普通字面量基本数据类型,不存在复杂原型链,只有包装对象才会生效。
手写简易版:
function myInstanceof(obj, fn) {
// 拿到构造函数的原型
const proto = fn.prototype;
// 拿到对象的隐式原型
let p = obj.__proto__;
// 沿着原型链一直往上找
while (p) {
if (p === proto) {
return true;
}
p = p.__proto__;
}
return false;
}
console.log(myInstanceof([], Array)); //true
console.log(myInstanceof(2, Array)); //false
console.log(myInstanceof([], Object)); //true
3.constructor
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
cnstructor有两个作用,一是判断数据的类型,二是对象实例通过constructor对象来访问它的构造函数。 注意!!!如果创建一个对象,来改变它的原型,constructor就不能用来判断类型了:
function Fn() { };
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn); // false
console.log(f.constructor === Array); // true
// ----------------------------------------------
function Student(name, age) {
this.name = name
this.age = age
}
const s1 = new Student('zhangsan', 18)
console.log(s1.hasOwnProperty('constructor')); //false
const array = new Array(1, 2, 3)
console.log(array.hasOwnProperty('constructor')); // false
注意!!!实例自身没有constructor,constructor是原型上的属性,那为什么能点出来f.constructor?
因为js的原型链查找机制:
- 先在实例自身查找constructor -> 找不到
- 去f. _proto(也就是Fn.prototype)上找
- constructor挂载在构造函数的prototype上
4, Object.prototype.toString.call()
var a = Object.prototype.toString;
console.log(a.call(2)); // [object Number]
console.log(a.call(true)); // [object Boolean]
console.log(a.call('str')); // [object String]
console.log(a.call([])); // [object Array]
console.log(a.call(function () {}));// [object Function]
console.log(a.call({})); // [object Object]
console.log(a.call(undefined)); // [object Undefined]
console.log(a.call(null)); // [object Null]
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
那为什么 {} .toString() 能直接输出 [object Object]?
普通对象没有重写 toString,
直接沿用 Object.prototype 原生方法,
所以 {}.toString() 和 Object.prototype.toString.call({}) 结果一样。
Object.prototype.toString.call(obj)原理
核心一句话:
调用Object 原型上未被重写的原生 toString 方法,通过 call 把方法内部 this 绑定为目标 obj,读取引擎内部内部类型标签 [[Class]] ,从而精准判断数据类型。
- 分步拆解原理
① 为什么不用 obj.toString() ?
JS 所有内置类型(Array、Number、Function、String…)
都重写了自身原型上的 toString,
直接调用执行的是自定义业务逻辑,不是类型检测。
- 数组:转逗号拼接字符串
- 函数:输出函数源码
- 数字 / 字符串:转文本
② Object.prototype.toString 是唯一原生原版
只有最顶层 Object 原型上的 toString 没有被重写,
它是 JS 引擎底层原生方法,专门用来检测类型。
③ 为什么必须加 .call(obj)?
-
该方法内部依赖 this,需要
this = 目标对象; -
直接调用
Object.prototype.toString(),this 指向Object.prototype; -
call(obj)强制修改 this 指向为传入的 obj,让方法作用在我们要检测的数据上。
④ 底层核心:读取内部 [[Class]]
这是关键(面试加分点)
引擎中每一个值都有一个不可访问、不可修改的内部私有属性 [[Class]] ,
用来标记真实内置类型:
Array / Number / String / Null / Undefined / Boolean / Function / Object
Object.prototype.toString 内部逻辑:
- 取当前
this - 读取底层内部标签
[[Class]] - 拼接成格式:
[object Xxx]返回
- 特殊处理(高频考点)
早期 null / undefined 没有正常原型,
普通方法无法检测,但原生 toString 底层做了特殊兼容:
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(undefined) // [object Undefined]
- 完整极简背诵版
Object.prototype.toString.call(obj)
- 因为数组、函数等都重写了自身
toString,无法用来判断类型; - 借用Object 原型上最原始、未被重写的 toString;
- 通过
call改变方法内部this为目标对象; - 底层读取引擎内部私有
[[Class]]类型标签; - 返回
[object 真实类型],实现全类型精准检测。