常见数据类型检测方法
/*
+ JS中检测数据类型的办法
+ typeof([value]): 检测数据类型的运算符
+ @1 返回结果是一个字符串, 其次字符串中包含对应的数据类型,
例如: "undefined" "number" "string" "boolean" "symbol" "bigint" "function" "object"
typeof typeof typeof [1,2,3,4] => "string"
+ @2 弊端
+ typeof null ==> "object"
+ typeof 对象 ==> 除函数对象被识别为 "function", 其余的对象返回的都是 "object"
+ typeof 未被声明的变量 ==> 不会报错, 而是 "undefined"
+ 除了这些以外, 用 typeof 检测原始值类型(除了null以外)[或者函数类型]还是非常 方便 准确 的
+ @3 检测原理
+ 所有的数据类型值在计算机底层都是按照二进制来进行存储的, 而 typeof 检测类型, 也是按照二进制
来进行检测的 [特点: 性能好]; 所有以 000 开始的, 都是对象类型, 再排查一下call, 实现的是函数
对象, 没实现的就是其余的对象..., 而null这个值存储的二进制都是 0, 所以也被识别为 "object"
+ @4 不准确
typeof 10 ==> 'number'
typeof new Number(10) ==> 'object'
+ instanceof: 检测当前实例是否属于这个类[临时拉来做数据类型检测]
+ @1 实例 instanceof 构造函数, 返回值 true/false
+ @2 可以做一些数据类型检测[实现对typeof的补充, 可以实现对象的细分]
+ 弊端
+ 无法基于instanceof检测是否为标准普通对象[纯粹对象: 直接是Object类的实例];
因为所有对象都是Object类的实例, 基于 'xxx instanceof Object' 检测的时候
返回的结果都是 true
+ 无法检测原始值类型; 因为基于 instanceof 检测的时候, 默认不会进行"装箱"操作
例如: 10 instanceof Number --> false
+ 检测的结果不一定严谨: 因为, 原型链以及所属构造函数是谁, 用户自己是可以改变的
+ ...
+ @4 当我们基于 "[value] instanceof [Ctor]" 运算符进行数据类型的检测的时候
+ 首先调用 [Ctor][Symbol.hasInstanceof]这个函数, 如果存在这个函数, 则直接基于这个
函数处理即可; 当代浏览器基本都有, 因为Symbol.hasInstanceof在Function.prototype中,
每一个构造函数都是 Function 的实例, 都可以调用Function.prototype[Symbol.hasInstanceof]
这个方法
+ 如果不存在这个函数, 浏览器会按照当前[value]原型链一层层向上查找, 直到找到Object.prototype为止,
查看[Ctor].prototype是否出现在它的原型链中, 如果出现了, 则结果是true, 说明[value]是[Ctor]的
实例, 反之则为false ...
+ @5 分析 instanceof 的优缺点 和 底层实现机制, 并重写 instanceof
+ constructor: 获取当前实例所属的构造函数
+ @1 [value].constructor 获取其构造函数, 验证是否为我们想检测的类
例如 [value].constructor === Array
+ @2 相比较 instanceof 来讲
+ 可以检测原始值类型(排除 null 和 undefined)
+ 可以检测是否为标准普通对象 [value].constructor === Object
+ 和 instanceof 一样, 检测的结果仅供参考 [constructor这个值是可以被肆意修改的]
+ Object.prototype.toString.call([value]): 专门检测数据类型的办法
+@1 调用 Object.prototype.toString 方法, 让方法中的this指向检测的值, 就是检测
当前值的数据类型: 返回结果是一个字符串 "[object ?]"
例如: Object.prototype.toString.call(10) ==> "[object Number]"
Object.prototype.toString.call(new Number(10)) ==> "[object Number]"
+@2 基本上所有数据类型, 所属构造函数的原型上都有 toString 方法, 一般都是用来转换为字符串的,
只有 Object.prototype.toString是用来检测数据类型的
+@3 它是所有检测数据类型的办法中, 最强大 最稳定 ... 的方式[除了写起来麻烦点]
+@4 即使constructor值被修改 或者 基于Object.setPrototypeof(ary, Object.prototype)
重定向实例的原型指向, 结果也是不变的!
+@5 返回结果是 "[object ?]"; "?" 会是啥呢?
+ 首先获取[value][Symbol.toStringTag] 属性值, 如果存在这个属性, 则这个属性值是啥, "?"就是啥
+ 如果没有这个属性, 一般 "?" 是当前实例所属的构造函数
+ Symbol.prototype & BigInt.prototype & Math & GeneratorFunction.prototype & Promise.prototype
& Set.prototype & Map.prototype ... 这些类的原型上, 都有 Symbol.toStringTag 这个属性
+@6 Object.prototype.toString 这个方法是用来检测数据类型的, 而且这个方法内部规定: 方法中的 this是谁,
我们就检测谁的类型, 所以我们基于call方法改变this指向
--------------------
Array.isArray([value]): 检测是否为数组
isNaN([value]): 检测是否为有效数字
*/
alert({
name: 'xxxx'
}) // ==> "[object Object]"
/*
+ alert会把编写的值转换为字符串再输出, 而对象toString的时候, 调用的是Object.prototype.toString
这个方法, 而这个方法时检测数据类型的
*/
/*
+ 真正转换标准普通对象为字符串
+ JSON.stringify() 把对象变为JSON格式字符串 ---> JSON.parse()
+ Qs.stringify 依托Qs第三方库, 我们把对象变为 urlencoded
+ 前后端数据通信的时候, 我们经常需要把对象变为指定的字符串格式, 传递给服务器; 或者把服务器返回的
指定格式字符串, 变为对象
*/
// ---------------------- Object.prototype.toString.call
let obj = {}
let 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(ary, Object.prototype)重定向
实例的原型指向, 结果也是不变的!!
*/
// let ary1 = [1, 2]
// ary1.constructor = 0
// console.log(ary1)
// console.log(toString.call(ary1)) // "[object Array]"
// let ary = [1, 2]
// Object.setPrototypeOf(ary, Object.prototype)
// console.log(ary)
// console.log(toString.call(ary)) // "[object Array]"
// const Fn = function () { }
// let f = new Fn()
// console.log(toString.call(f)) // "[object, Object]"
/* 面试题
+ 需求: 期望自己写自定义构造函数, 所创建出来的实例在检测数据类型的时候,
可以返回的是 [object 自己的构造函数]
*/
const Fun = function () { }
Fun.prototype[Symbol.toStringTag] = "Fun"
let fun = new Fun()
console.log(toString.call(fun)) // "[object, Fun]"
//------------------------ constructor
// let arr = []
// let reg = /^$/
// let num = 10
// console.log(arr.constructor === Array) // true
// console.log(reg.constructor === Object) // false
// console.log(num.constructor === Array) // false
// console.log(num.constructor === Number) // true
// ----------------------- instanceof
// let obj = {}
// let arr = []
// let reg = /^$/
// let num = new Number(10)
// console.log(arr instanceof Array) // true 只有arr是一个数组
// console.log(obj instanceof Array) // false
// console.log(reg instanceof Array) //false
// console.log(num 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(num instanceof Object) // true
// let n = 20
// // n.toFixed(2) => 默认将n进行装箱 new Number(n) ==> new Number(n).toFixed(2)
// console.log(n 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
// const Fn = function Fn () {
// this.name = 'zhufeng'
// }
// Fn.prototype.sayName = function () { }
// Fn.xxx = 'xxx'
// Fn[Symbol.hasInstance] = function () { // 这样设置是无效的
// console.log(1)
// return false
// }
// Fn.prototype[Symbol.hasInstance] = function () { // 这样设置也是无效的
// console.log(1)
// return false
// }
// let f = new Fn()
// console.log(f instanceof Fn)
// 重构Symbol.hasInstance
// 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)
/**
* 检测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
// }