前言
通过本文你可以了解到多种检测数据类型的方法;这些方法的优缺点及其检测原理;从而运用更加自如.
一、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'