数据类型检测

341 阅读7分钟

数据类型

  • 原始值类型「值类型/基本数据类型」
    • Number 数字
    • Boolean 布尔
    • String 字符串
    • Null 空对象指针
    • Undefined 未定义
    • Symbol 唯一值 ???
    • Bigint 大数 ???
  • 对象类型「引用数据类型」
    • 标准普通对象 object
    • 标准特殊对象 Array、RegExp、Date、Math、Error……
    • 非标准特殊对象 Number、String、Boolean……
    • 可调用/执行对象「函数」function

常用的数据类型检测方法有哪些?

1. typeof 运算符

语法:

var str = typeof [value]

作用: 用来检测给定变量的数据类型

返回值: 一个字符串,字符串中包含了对应的数据类型 。

始终返回以下某个字符串:

  • 'undefined' => 值未定义
  • 'boolean' => 布尔值
  • 'number' => 数值
  • 'string' => 字符串
  • "symbol" =>唯一值
  • 'bignit' => 大数
  • 'function' => 函数是一种对象,不是一种数据类型
  • 'object' => 被检测的值是对象或者null

原理(底层机制): 所有数据类型的值在存储时,在计算机底层都是按照“二进制”来存储的「64位」

typeof 检测数据类型,就是按照存储的“二进制值”来进行检测的, 比如:

  • 000 开头的 => 对象
  • 1 开头的 => 整数
  • 010 开头的 => 浮点数
  • 100 开头的 => 字符串
  • 110 开头的 => 布尔值
  • 000000…. => null

如值的前三位是 000 的,都被认为是对象。在这个基础上,再检测对象内部是否实现了[[Call]]方法

  • 实现了,则认为是函数,返回“function”。
  • 没有实现,则都返回 "object"。

优点:

  • 因为 typeof 检测数据类型,就是按照存储的“二进制值”来进行检测的,因此使用 typeof 检测数据类型的时候,性能相对好一些。
  • 在检测原始值类型的值( null 除外)时,结果都是准确的。

缺点(局限性):

  • 使用 typeof 检测 null 时返回的是 'object'
  • 使用 typeof 检测对象类型的值时,不能细分对象。除函数对象返回“function”外,其他的对象类型值返回的都是“object”。
  • 使用 typeof 检测一个未被声明的变量,不会报错,而是返回“undefined”。

2. instanceof 操作符

我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象。

语法:

var result = value instanceof Ctor
// Ctor => constructor  构造函数

作用:
检测某个实例是否属于这个类。可以弥补 typeof 的不足,把对象类型进行细分。

返回值:

  • true => 实例属于这个类
  • false => 实例 不属于这个类

原理(底层机制):
基于 instanceof 检测的时候,先看 Ctor 构造函数是否存在 Symbol.hasInstance 这个属性方法,

  • 如果存在则基于这个方法进行检测,则调用 Ctor[Symbol.hasInstance](value)
  • 如果没有这个属性方法,获取value的原型链(直到找到Object.prototype为止)
    • 如果 Ctor.prototype 出现在它的原型链上,则证明value是Ctor的实例,
    • 反之则不是...
  • 在新版本浏览器中,在 Function.prototype 上存在一个属性方法 Symbol.hasInstance,所以只要是函数「不论是普通函数,还是构造函数」,都具备 Symbol.hasInstance 这个方法...

缺点(局限性):

  • instanceof 来检测数据类型,就是临时“凑个数”,所以答案仅供参考。
  • instanceof 检测任何引用值 和 Object 构造函数都会返回 true。因为所有引用值都是 Object 的实例。
  • instanceof 检测原始值,则始终会返回 false,因为原始值不是对象。

例1:

let n = [];
console.log(n instanceof Array); //true
console.log(n instanceof RegExp); // false
console.log(n instanceof Object); //true  所有引用值都是 Object 的实例。

例2:

自定义构造函数,如果借用其他构造函数的原型对象,会致使检测结果不准。

下面代码中的构造函数 Fn ,借用了 Array.prototype。

function Fn() {}
Fn.prototype = Array.prototype;
let f = new Fn;
console.log(f instanceof Array); // true  检测结果不准了。

例3:

内置类的 Symbol.hasInstance 属性,重写是无效的


Array[Symbol.hasInstance] = function () {
    return 100;
};
let n = [];
console.log(n instanceof Array);  //=> true

例4:

自定义构造函数,可以指定 Symbol.hasInstance 属性。可能会致使检测结果不准。

class Fn {
    static[Symbol.hasInstance](val) {
        return false;
    }
}
let f = new Fn; // f 是构造函数  Fn 的实例
console.log(f instanceof Fn); //false
console.log(Fn[Symbol.hasInstance](f)); //false

3. constructor

语法:

var result = 实例对象.constructor ;

作用: 获取实例的构造函数。

返回值: 实例对象所属的构造函数(堆地址)

原理(底层机制):

大多数函数都有一个 prototype 属性(原型对象),其下面有个 constructor 属性。

默认情况下,constructor 属性指回与之关联的构造函数。

缺点(局限性):

  • constructor 来检测数据类型也是临时拉来凑数的,所以也不靠谱。
  • constructor 属性的指向可以被肆意更改。
  • null 在访问 constructor 属性时,会报错。

例1:

可以用 constructor检测一个对象是否为标准普通对象「纯粹对象」, 是否为 Object 的直属实例。

n 是 Array 的直属实例, 而不是 RegExp 和 Object 的直属实例。

let n = [];
console.log(n.constructor === Array); // true
console.log(n.constructor === RegExp); // false
console.log(n.constructor === Object);  // false

例2:

使用原始值访问constructor 属性时,会默认进行“装箱”操作。

所谓“装箱”是指原始值转换为其构造函数创造的对象类型实例,从而原始值就可以调用所属类原型上的方法了。

let n = 1;
console.log(n.constructor === Number);
// true
//  把原始值的1默认变为对象类型的实例 new Number(1)

例3:

修改了 constructor 属性的指向 ,检测结果不准了

let n = [];
Array.prototype.constructor = 'AAA'; //=>false
console.log(n.constructor === Array);

例4:

null 在访问 constructor 属性时,会报错

(null).constructor
// Uncaught TypeError: Cannot read property 'constructor' of null

4. Object.prototype.toString.call([value])

语法:

作用: 大部分类的原型上都有toString方法,都是用来转换为字符串的...

但是Object.prototype.toString不是用来转换为字符串的,而是检测数据类型的

返回值: “[object ?]”

原理(底层机制): 先检查[value][Symbol.toStringTag]属性,

  • 如果有这个属性,属性值是啥 @X,最后检测的结果就是 “[object @X]”。
    • 如果没这个属性,则按照自己所属的内置类进行处理

优点:

这个方法忒好用了...除了写起来麻烦一些,几乎没有漏洞

例1:

调用 Object.prototype.toString() 时,方法中的this是谁,就是检测谁的数据类型

// 1.
let obj = {
    name: 'xiaoming'
}
console.log(obj.toString());
//=> "[object Object]" -> Object.prototype.toString


// 2. 通过 call() 改变方法执行时的 this 指向
Object.prototype.toString.call([10, 20])
// =>"[object Array]"

// 3.  实际开发中,为了简化代码,还会这样写
let obj = {},
    toString = obj.toString; // => Object.prototype.toString
console.log(toString.call(1)); // => "[object Number]"

例2:

Number.prototype.toString 把数组实例 转换为字符串

  • 数字.toString() 把数字转换为字符串
  • 数字.toString(radix) 把数字转换为radix进制值的字符串
// 不指定 radix, 默认为十进制数
console.log((2).toString());
// "2"

// 指定 radix 为 2, 先把数值转为2进制数,再转成字符串
console.log((2).toString(2));
// "10"

例3:

console.log([10, 20, 30].toString());
//-> "10,20,30" -> Array.prototype.toString

例4:

为自定义构造函数的原型对象上添加属性 Symbol.toStringTag

class Fn {
    constructor() {
        this.name = 'zhufeng';
        this[Symbol.toStringTag] = 'Fn';
    }
}
let f = new Fn;
console.log(toString.call(f)); //=>“[object Fn]”

封装几个用于数据检测的函数

1. 检测是否是一个函数

var isFunction = function isFunction(obj) {
    return typeof obj === 'function' && typeof obj.nodeType !== "number" &&  typeof obj.item !== "function"
}

第一个条件 typeof obj === 'function' ,已经判断出obj是函数了,为什么还要加 2 个判断条件呢? 那是为了处理两个兼容问题:

  1. typeof document.createElement("object")==="function"

  2. typeof document.getElementsByTagName("div")==="function"

    HTML 中有个` <object>` 元素。使用<object> 元素可在 HTML 加入 Flash 文件。有些浏览器会判定<object> 元素为函数,所以我们需要排除 <object> 元素。那么如何才能排除呢?
     在 JavaScript中,所有 DOM 节点类型都继承 Node 类型。也就是说每个 DOM 节点都能访问到 Node.prototype 上的属性和方法。而 Node.prototype 上就有一个 nodeType 属性,表示该节点的类型。节点类型由定义在 Node 类型上的 12 个数值常量表示。所以不管是什么类型的节点,返回的值类型都是 'number',即 `typeof obj.nodeType !== "number"` 。
    
    
     通过 `document.getElementsByTagName() ` 获取到的是一个 `HTMLCollection` 元素的集合。
     `HTMLCollection` 提供了用来从该集合中选择元素的方法和属性。
     其中有个静态方法 item(),其作用是根据给定的索引(从0开始),返回具体的节点。
    

2. 检测是否 window 对象

const isWindow = function isWindow(obj) {
        return obj !=null && obj === obj.window
    }

3. 检测数据类型的方法(十八项全能)


let toString = {}.toString;
// {}.toString => Object.prototype.toString

const toType = function toType(obj) {
        let reg = /^\[object ([0-9a-zA-Z]+)\]$/;
        // null & undefined
        if (obj == null) return obj + '';
        // 其他类型的
        return typeof obj === 'object' || typeof obj === 'function' ? reg.exec(toString.call(obj))[1].toLowerCase() : typeof obj ;
    }

console.log(toType(0)); // 'number'
console.log(toType(/^$/));  // 'regexp'