js中数据类型检测专题汇总(typeof、instanceof、constructor、Object.prototype.toString.call([valu

380 阅读6分钟

前言

通过本文你可以了解到多种检测数据类型的方法;这些方法的优缺点及其检测原理;从而运用更加自如.

一、typeof[value]:检测数据类型的运算符

1、特点

返回的是一个字符串,其字符串包含对应的数据类型,例如:number,object,function 。。。。

(1)typeof null=>"object"

(2)typeof 不能细分对象类型,除函数对象会返回""function",检测其余对象都会返回object含非标准特殊对象 new Number(1)

(3)typeof检测一个未被申明的变量,结果不会报错,而是字符串“undefined”

(4)typeof typeof typeof [1000] //'string'

2、弊端

@1、typeof null => object

@2、typeof 对象=> 除函数对象吧被识别为function,其他对象都是返回object

@3、typeof 为声明的变量->不会报错 而是undefined

3、检测的原理

所有数据类型值在计算机底层都是基于“二进制”形式来进行存储的【64操作系统】而typeof就是基于存储的二进制值进行检测的【好处:性能好】,它会识别二进制值得前三位,如果前三位数字都是0,就会按对象处理【识别为对象后,看对象有没有实现[call]方法,实现了这个方法则被解析为"function",否则都是"object"...】而null存储的二进制值是64个零,前三位也是零,所以也被识别为"object"

思考:0.2+0.1===0.3 //false

0.1+0.3===0.4 //true

优点:性能好

4、应用

(1)检测除null以外的原始数据类型、检测函数类型,优先选择typeof检测

if(obj!==null&&/^(object|function)$i.test(typeof obj)){

//obj是对象或函数}else{//原始值类型}

(2)检测js代码运行的环境【浏览器(webview)、webpack、node......】

if(typeof window!=="undefined"){

//浏览器或者webpack环境

}

if(typeof module === "object" && typeof module.exports==="object"){

//支持Commonjs模块化规范的环境 【webpack、node...】

}

二、instanceof:检测当前实例所属的构造函数

[实例]instanceof[构造函数]->检测当前实例是否属于这个构造函数(类)

1、特点

@1、实例 instantceof 构造函数 返回true/false

@2、可以做一些数据类型的检测「实现对typeof的补充,可以实现对象的细分」

代码如下(示例):

let obj = {};
let arr = [];
let reg = /^$/;
let num = new Number(10);

console.log(arr instanceof Array); //true 
console.log(obj instanceof Array); //false
console.log(reg instanceof Array); //false
console.log(typeof num); //"object"
console.log(num instanceof Number); //true 说明num是Number类的一个实例「原始值对应的对象类型结果」

console.log(arr instanceof Array); //true 
console.log(arr instanceof Object); //true  
console.log(reg instanceof Object); //true  
console.log(obj instanceof Object); //true  
console.log(num instanceof Object); //true  

2、弊端

  • 无法基于instanceof检测是否为标准普通对象「纯粹对象:直接是Object类的实例」;因为所有对象都是Object类的实例,基于“xxx instanceof Object” 检测的时候,返回结果都是true;
  • 无法检测原始值类型的值;因为基于instanceof检测的时候,默认不会进行“装箱”操作!! 例如:10 instanceof Number -> false
  • 检测的结果不一定严谨;因为,原型链以及所属构造函数是谁,是可以用户自己去改变的!!
const Fn = function Fn() {};
Fn.prototype = Array.prototype;
let f = new Fn;
console.log(f); //从结构来看,f一定不是数组「数组的结构:数字索引、逐级递增、length属性...」
console.log(f instanceof Array); //true 

3、检测原理
当我们基于 “[value] instanceof [Ctor]” 运算符进行数据类型的检测的时候

  • 首先调用 [Ctor]Symbol.hasInstance 这个函数,如果存在这个函数,则直接基于这个函数处理即可
    当代浏览器基本都有,因为Symbol.hasInstance在Function.prototype中,每一个构造函数都是Function的实例,都可以调用Function.prototype[Symbol.hasInstance]这个方法
  • 如果不存在这个函数,浏览器会按照当前[value]原型链一层层向上查找,直到找到Object.prototype为止;查看[Ctor].prototype是否出现在它的原型链中,如果出现了,则结果是true,说明[value]是[Ctor]的实例,反之则为false…
const Fn = function Fn() {
    this.name = 'zhufeng';
};
Fn.prototype.sayName = function () {};
Fn.xxx = 'xxx';
Fn[Symbol.hasInstance] = function () { //这样设置是无效的
    console.log(1);
    return false;
};
Function.prototype[Symbol.hasInstance] = function () { //这样设置也是无效的
    console.log(1);
    return false;
};
let f = new Fn;
console.log(f instanceof Fn);

这种写法无效的

class Fn {
    name = 'zhufeng';
    sayName() {}
    // 当做对象,设置静态私有的属性方法「这样设置是有用的,所以重构“构造函数”的Symbol.hasInstance,只支持ES6中class创建的类,ES5中创建的构造函数不支持这样重构」
    static xxx = 'xxx';
    static[Symbol.hasInstance](obj) {
        console.log(obj);
        return false;
    }
}
let f = new Fn;
console.log(f instanceof Fn); 


当做对象,设置静态私有的属性方法「这样设置是有用的,所以重构“构造函数”的Symbol.hasInstance,只支持ES6中class创建的类,ES5中创建的构造函数不支持这样重构」<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

4、重写instanceof

// instance_of:检测value是否为Ctor的实例
// value:要检测的实例
// Ctor:要检测的构造函数
const instance_of = function instance_of(value, Ctor) {
    // 保证Ctor的格式也是正确的
    if (typeof Ctor !== "function") throw new TypeError('Right-hand side of instanceof is not callable');
    if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check');

    // 不支持原始值类型值的检测
    if (value === null) return false;
    if (!/^(object|function)$/.test(typeof value)) return false;

    // 支持Symbol.hasInstance方法的直接按照这个处理
    if (typeof Ctor[Symbol.hasInstance] === "function") return Ctor[Symbol.hasInstance](value);

    // 不支持的则按照原型链一层层的查找即可  Object.getPrototypeOf(value):获取value所属构造函数的原型对象
    let proto = Object.getPrototypeOf(value);
    while (proto) {
        // Ctor.prototype出现在了value的原型链上「value是Ctor的实例对象」:直接返回true & 结束查找
        if (proto === Ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};

三、construct 获取当前实例所属的构造函数

[实例].constructor===[构造函数],也不准确,因为constructor值可以被肆意修改的,不常用。\

@1 [value].constructor 获取其构造函数,验证是否为我们想检测的类
例如:[value].constructor===Array
@2 相比较于instanceof来讲

\

  • 可以检测原始值类型的值「排除null/undefined」
  • 检测是否为标准普通对象 [value].constructor===Object
  • 和instanceof一样,检测的结果仅供参考「constructor这个值是可以被肆意修改的」\
 let arr = [];
 let reg = /^$/;
 let num = 10;

console.log(arr.constructor === Array); //true
console.log(arr.constructor === Object); //false
console.log(reg.constructor === Array); //false
console.log(num.constructor === Number); //true

四、 Object.prototype.toString.call([value]):专门检测数据类型的办法

let obj = {},
toString = obj.toString; //->Object.prototype.toString

基本上所有的数据类型,所属构造函数的原型上都有toString方法,一般都是用来转换为字符串的,只有Object.prototype.toString是用来检测数据类型的

console.log(toString.call(10)); //"[object Number]"
console.log(toString.call(new Number(10))); //"[object Number]"
console.log(toString.call("zhufeng")); //"[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(function () {})); //"[object Function]"
console.log(toString.call(new Date())); //"[object Date]"
console.log(toString.call(new Error())); //"[object Error]"
console.log(toString.call(Math)); //"[object Math]"
console.log(toString.call(function* () {})); //"[object GeneratorFunction]"
console.log(toString.call(Promise.resolve())); //"[object Promise]"
// 即使constructor值被修改 或者 基于Object.setPrototypeOf重定向实例的原型指向,结果也是不变的!!所以 toString.call 这种办法检测的结果是非常可靠的!!

@1 调用Object.prototype.toString方法,让方法中的this指向检测的值,就是检测当前值的数据类型;返回结果是一个字符串 “[object ?]”

例如:Object.prototype.toString.call(10) -> “[object Number]”
Object.prototype.toString.call(new Number(10)) -> “[object Number]”

它是所有检测数据类型的办法中,最强大、最稳定…的方式{除了写起来麻烦一些}

\

@2 返回结果是 “[object ?]”,“?” 会是啥呢?

\

  • 首先获取[value][Symbol.toStringTag]属性值,如果存在这个属性,则这个属性值是啥,“?”就是啥!
  • 如果没有这个属性,一般“?”是当前实例所属的构造函数!!
    Symbol.prototype & BigInt.prototype & Math &GeneratorFunction.prototype & Promise.prototype & Set.prototype &Map.prototype … 这些类的原型上,都有Symbol.toStringTag这个属性!!

Object.prototype.toString这个方法是用来检测数据类型的,而且方法内部规定:方法中的this是谁,我们就检测谁的类型,所以我们基于call方法去改变this指向!!!

 const Fn = function Fn() {};
  let f = new Fn;
 console.log(toString.call(f)); //“[object Object]”

需求:期望自己写自定义构造函数,所创建出来的实例在检测数据类型的时候,可以返回的是“[object 自己的构造函数]”

const Fn = function Fn() {};
Fn.prototype[Symbol.toStringTag] = "Fn";
let f = new Fn;
console.log(toString.call(f)); //“[object Fn]” 
alert({
    name: 'xxx'
});  //=>“[object Object]”   alert会把编写的值转换为字符串宰输出,而对象toString的时候,调用的是Object.prototype.toString这个方法,而这个方法是检测数据类型的

真正转换标准普通对象为字符串

JSON.stringify 把对象变为 JSON 格式字符串 ->JSON.parse

Qs.stringify 依托Qs第三方库,我们把对象变为 urlencoded 格式字符串

前后端数据通信的时候,我们经常需要把对象变为指定的字符串格式,传递给服务器;或者把服务器返回的指定格式字符串,变为对象!!

let obj = {
    name: 'zhufeng',
    age: 12,
    teacher: 'zhou'
};
// console.log(JSON.stringify(obj)); //'{"name":"zhufeng","age":12,"teacher":"zhou"}'
console.log(Qs.stringify(obj)); //'name=zhufeng&age=12&teacher=zhou'