JS 数据类型 - 类型判断

82 阅读4分钟

最终函数

function getType(value) {
  if (value === null) { return 'null' }             // 处理 null 的特殊情况
  const basicType = typeof value
  if (basicType !== 'object') { return basicType }  // 处理基本数据类型
  if (value instanceof Date) { return 'date' }      // 处理日期对象
  if (value instanceof RegExp) { return 'regexp' }  // 处理正则表达式对象
  if (Array.isArray(value)) { return 'array' }      // 处理数组
  const objectType = Object.prototype.toString.call(value)
  return objectType.slice(8, -1).toLowerCase()      // 处理其他对象
}

// 测试
getType(123)               // number
getType(1.23)              // number
getType(NaN)               // number
getType(Infinity)          // number
getType('demo1')           // string
getType("demo2")           // string
getType(`demo3`)           // string
getType(true)              // boolean
getType(false)             // boolean
getType(null)              // null
getType(undefined)         // undefined
getType(Symbol(123))       // symbol
getType(Symbol('demo'))    // symbol
getType(123n)              // bigint
getType(()=>{}))           // function
getType(function(){})      // function
getType({})                // object
getType([])                // array
getType(new Date())        // date
getType(new RegExp('123')) // regexp
getType(/123/g)            // regexp

typeof

​typeof​ 运算符返回一个字符串,表示操作数的类型。

对于原始类型来说,除了 null 都可以显示正确的类型;对于对象类型来说,除了函数都会显示 object。

PS1: 在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"。

PS2:使用 typeof 时,加空格(不加括号)和加括号在功能上没有本质区别,但在语法和语义上存在一些细微差异。加空格(不加括号)是 typeof 最常见的使用方式,直接跟要检测的变量或值。加括号的形式类似于函数调用,但 typeof 不是函数,只是语法上看起来像函数调用,括号的作用是将括号内的表达式视为一个整体,先计算表达式的值,再对该值进行类型检测

typeof(123)                // number --> 加括号
typeof 123                 // number --> 加空格(不加括号)
typeof 1.23                // number
typeof NaN                 // number 
typeof Infinity            // number
typeof 'demo1'             // string
typeof "demo2"             // string
typeof true                // boolean
typeof false               // boolean
typeof null                // object --> null 代表的是空指针, 类型标签是 0
typeof undefined           // undefined
typeof Symbol(123)         // symbol 
typeof Symbol('demo')      // symbol
typeof 123n                // bigint
typeof (()=>{})            // function
typeof (function(){})      // function
typeof {}                  // object 
typeof []                  // object
typeof new Date()          // object
typeof new RegExp('123')   // object
typeof /123/g              // object

instanceof

​instanceof​ 运算符用于检测构造函数的 prototype 属性​(即constructor.prototype​)是否出现在某个实例对象(object)​的原型链上。

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype,即判断一个对象是否是某一数据类型的实例。instanceof 可以精准判断引用数据类型(Array,Function,Object),而基本数据类型不能被instanceof精准判断。

123 instanceof Number               // false
1.23 instanceof Number              // false
NaN instanceof Number               // false
Infinity instanceof Number          // false
'demo1' instanceof String           // false
"demo2" instanceof String           // false
true instanceof Boolean             // false
false instanceof Boolean            // false
null instanceof Null                // ReferenceError: Null is not defined
undefined instanceof Undefined      // ReferenceError: Undefined is not defined
Symbol(123) instanceof Symbol       // false
Symbol('demo') instanceof Symbol    // false
123n instanceof BigInt              // false
(()=>{}) instanceof Function        // true
(function(){}) instanceof Function  // true
({}) instanceof Object              // true
([]) instanceof Array               // true
new Date() instanceof Date          // true
new RegExp('123') instanceof RegExp // true
/123/g instanceof RegExp            // true

constructor

Object 实例的 constructor​ 数据属性返回一个引用,指向创建该实例对象的构造函数。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

可以通过访问对象的 constructor​ 属性,再和特定的构造函数进行比较,从而判断对象的类型。

(123).constructor === Number             // true
(1.23).constructor === Number            // true
NaN.constructor === Number               // true
Infinity.constructor === Number          // true
'demo1'.constructor === String           // true
"demo2".constructor === String           // true
true.constructor === Boolean             // true
false.constructor === Boolean            // true
null.constructor === Null                // TypeError: Cannot read properties of null (reading 'constructor')
undefined.constructor === Undefined      // TypeError: Cannot read properties of undefined (reading 'constructor')
Symbol(123).constructor === Symbol       // true
Symbol('demo').constructor === Symbol    // true
123n.constructor === BigInt              // true
(()=>{}).constructor === Function        // true
(function(){}).constructor === Function  // true
{}.constructor === Object                // true
[].constructor === Array                 // true
new Date().constructor === Date          // true
new RegExp('123').constructor === RegExp // true
/123/g.constructor === RegExp            // true

​constructor​ 属性可被修改:constructor​ 属性是可以被修改的,一旦被修改,基于 constructor​ 的类型判断就会失效。

function Person(name) {
  this.name = name
}
const person = new Person('John')
person.constructor = Array 
console.log(person.constructor === Person) // 输出: false

Object.prototype.toString.call()

​toString()​ 方法返回一个表示该对象的字符串。该方法旨在重写(自定义)派生类对象的类型转换的逻辑。

使用 Object 对象的原型方法 toString ,使用 call 进行狸猫换太子。

var toStr = Object.prototype.toString
toStr.call(123)                   // [object Number]
toStr.call(1.23)                  // [object Number]
toStr.call(NaN)                   // [object Number]
toStr.call(Infinity)              // [object Number]
toStr.call('demo1')               // [object String]
toStr.call("demo2")               // [object String]
toStr.call(true)                  // [object Boolean]
toStr.call(false)                 // [object Boolean]
toStr.call(null)                  // [object Null]
toStr.call(undefined)             // [object Undefined]
toStr.call(Symbol(123))           // [object Symbol]
toStr.call(Symbol('demo'))        // [object Symbol]
toStr.call(123n)                  // [object BigInt]
toStr.call(()=>{})                // [object Function]
toStr.call(function(){})          // [object Function]
toStr.call({})                    // [object Object]
toStr.call([])                    // [object Array]
toStr.call(new Date())            // [object Date]
toStr.call(new RegExp('123'))     // [object RegExp]
toStr.call(/123/g)                // [object RegExp]

​toString ​也不是完全可靠的。

对象可以通过定义 Symbol.toStringTag​ 属性来更改 Object.prototype.toString() ​的行为,从而导致意想不到的结果。

const myDate = new Date();
Object.prototype.toString.call(myDate)       // [object Date]

myDate[Symbol.toStringTag] = "myDate"
Object.prototype.toString.call(myDate)       // [object myDate]

Date.prototype[Symbol.toStringTag] = "prototype polluted"
Object.prototype.toString.call(new Date())   // [object prototype polluted]