js数据类型检测的方式有哪些

5 阅读4分钟

数据类型检测的方式有哪些

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]] ,从而精准判断数据类型。

  1. 分步拆解原理

① 为什么不用 obj.toString()

JS 所有内置类型(Array、Number、Function、String…)

都重写了自身原型上的 toString

直接调用执行的是自定义业务逻辑,不是类型检测。

  • 数组:转逗号拼接字符串
  • 函数:输出函数源码
  • 数字 / 字符串:转文本

Object.prototype.toString 是唯一原生原版

只有最顶层 Object 原型上的 toString 没有被重写,

它是 JS 引擎底层原生方法,专门用来检测类型

③ 为什么必须加 .call(obj)

  1. 该方法内部依赖 this,需要 this = 目标对象

  2. 直接调用 Object.prototype.toString(),this 指向 Object.prototype

  3. call(obj) 强制修改 this 指向为传入的 obj

    让方法作用在我们要检测的数据上。

④ 底层核心:读取内部 [[Class]]

这是关键(面试加分点)

引擎中每一个值都有一个不可访问、不可修改的内部私有属性 [[Class]]

用来标记真实内置类型:

Array / Number / String / Null / Undefined / Boolean / Function / Object

Object.prototype.toString 内部逻辑:

  1. 取当前 this
  2. 读取底层内部标签 [[Class]]
  3. 拼接成格式:[object Xxx] 返回
  1. 特殊处理(高频考点)

早期 null / undefined 没有正常原型,

普通方法无法检测,但原生 toString 底层做了特殊兼容

Object.prototype.toString.call(null)      // [object Null]
Object.prototype.toString.call(undefined) // [object Undefined]
  1. 完整极简背诵版
Object.prototype.toString.call(obj)
  1. 因为数组、函数等都重写了自身 toString,无法用来判断类型;
  2. 借用Object 原型上最原始、未被重写的 toString
  3. 通过 call 改变方法内部 this 为目标对象;
  4. 底层读取引擎内部私有 [[Class]] 类型标签;
  5. 返回 [object 真实类型],实现全类型精准检测。