JS类型判断:一篇彻底整明白!

62 阅读5分钟

JS类型判断:一篇彻底整明白!

1. 引言:为什么我写的代码总在深夜报复我?

你有没有经历过这样的绝望时刻:上线前夜,你的代码突然崩了,只因一个不起眼的undefined悄悄混入了数字大家庭?或者面试时,面试官露出神秘的微笑问:"typeof null为什么是object啊?",你内心OS:"这js是不是有毛病?"

别担心,今天我们就来把JS类型判断这个"玄学"问题,彻底变成"小学数学"!

2. JavaScript数据类型:一家子奇葩成员

JS的数据类型家族,可以说是个"奇葩说"现场:

2.1 基本类型(老实人阵营)

  • String:我是文字工作者
  • Number:我是搞数学的(包括NaN这个"不是数字的数字")
  • Boolean:我非黑即白,绝不暧昧
  • Undefined:我...我还没想好我是谁
  • Null:我是有意的空,跟上面那位不一样!
  • Symbol:我是ES6来的独一无二的靓仔
  • BigInt:我是能处理大数字的大块头

2.2 引用类型(社会人阵营)

  • Object:我是万物的基础
  • Array:我喜欢排排坐
  • Function:我能干活
  • Date:我管时间
  • 其他七大姑八大姨...

3. 类型判断四大天王(各有各的脾气)

3.1 typeof操作符:快但眼神不好

let n = 123
let s = 'hello'
let f = true
let u = undefined
let nu = null
let sy = Symbol(1)
let big = 2345676543212345n

let arr = []
let obj = {}
let fn = function(){}
let date = new Date()

console.log(typeof n);
console.log(typeof(s));
console.log(typeof f);
console.log(typeof u);
console.log(typeof sy);
console.log(typeof big);
console.log(typeof nu); //会得到 object

console.log(typeof arr); //会得到 object
console.log(typeof obj); //会得到 object
console.log(typeof date); //会得到 object
console.log(typeof fn); //会得到 function

这里我们可以看到在js这门语言中,typeof 可以识别出除去 null 类型的数据类型,但是还会有其他的类型识别并不准确例如,就像 arr 被识别为 object ,识别粗来了吗?出来了,但是没完全出来。毕竟js万物皆对象,归在 object 这个大祖宗里也情有可原。 那我们来聊一聊为什么 typeof 会出现这种情况,甚至有 null 这个原始遗留问题呢?

在JS的底层实现中(以 V8 引擎为例),数据是以二进制形式存储的,并且通过二进制低位的"类型标签"(type tag)来区分不同的数据类型。引用类型(包括对象、数组、日期、函数以外的所有引用类型)在二进制表示中的前三位通常都是 000 ,因此 typeof 操作符在检测到这一模式时会返回 object 。而函数呢?它又是为什么会识别为 Function 呢?这是因为函数识别会有特殊的编码,前三位是 100 ,而非 000 不会被识别为 object 。至于 null 呢?则被识别为一串 0 ,前三位当然也是 000 ,也被识别为 object

3.2 instanceof:认亲专业户但有点势利眼

let n = 123
let s = 'hello'
let f = true
let u = undefined
let nu = null
let sy = Symbol(1)
let big = 2345676543212345n

let arr = []
let obj = {}
let fn = function(){}
let date = new Date()

console.log(arr instanceof Array) //会得到 true
console.log(obj instanceof Object) //会得到 true
console.log(date instanceof Date) //会得到 true 
console.log(fn instanceof Function) //会得到 true
console.log(n instanceof Number) //会得到 false

对于我们的 instanceof 呢,它只识别引用类型,并且返回的是 Boolean 的结果,对于原始类型 instanceof 只会说: false 。对于 instanceof 来说,它对于数据类型的判断是追根溯源的。

举例来说

let arr = []
console.log(arr instanceof Array) //会得到 true
console.log(arr instanceof Object) //会得到 true

为什么会这样呢?这是因为 instanceof 寻亲的原理是找原型,我们都知道 arr 在v8 里实际上是创建了一个 Array 的实例对象,我自然可以在 arr 的隐式原型上找到想要的,因为

arr.[[proto]] == Array.prototype

Object 就更像祖宗的祖宗了,因为

arr.[[proto]].[[proto]] == Object.prototype

在这里大概就能了解 instanceof 寻亲的原理了

3.3 Object.prototype.toString.call():终极裁判官

let n = 123
let s = 'hello'
let f = true
let u = undefined
let nu = null
let sy = Symbol(1)
let big = 2345676543212345n

let arr = []
let obj = {}
let fn = function(){}
let date = new Date()

console.log(Object.prototype.toString.call(date));//会得到 [object Date]

function getType(x){
    const val = Object.prototype.toString.call(x)
    const valType = val.slice(8,-1)
    return valType
}

console.log(getType(s));//会得到 String

这是什么原因呢?具体可见官方文档对于 Object.prototype.toString.call() 方法的描述,笔者仅在此处表达自己的理解

Object.prototype.toString

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  1. toStringthis 值作为参数传递给 ToObject ,设 O 为调用 ToObject 的结果
  2. 设一个变量 classO[[class]] 的内部属性值
  3. 返回一个字符串,这个字符串由'[object ' + class + ']', 一种结构它的内部属性 [[class]] 对应的值 就是 创建它的那个构造函数

前两句话是官方文档里的内容,对于后面几点的理解,先给出一段伪代码(跑不了的)

function toString() {
  const O = ToObject(this)
  // {
  //   x: dasdas
  //   h: dadas,
  //   [[class]]: Object
  // }
  // const class = O.[[class]]
}

Object.prototype 上挂了个 toString ,在 toString 跑的时候,会整个对象 O 出来,里面就有 [[class]] 这个属性值,里面存着的便是 this 的类型。在加 .call 前,由于 toString 是被 prototype 原型(也是对象)调用,故 this 的类型就是一个对象,也就是 objectObject.prototype.toString.call() 在加了 .call 后,会使得 this 指向 () 内内容,进而实现准确的类型判断。

3.4 Array.isArray()

这个局限性就很强了,只能判断数组判断原理呢?大概是这样

<!-- x.__proto__ === Array.prototype>

4. 总结:类型判断的终极哲学

  1. 没有完美的判断方法,只有最适合当前场景的方法
  2. 了解原理比死记硬背更重要,理解了为什么,怎么用都清楚
  3. 在实际开发中,根据需求选择合适的方法,不要过度设计
  4. 类型判断就像是在JavaScript世界里的"识人术",学会了它,你就能:
  • 避免深夜加班改bug
  • 通过面试官的灵魂拷问
  • 写出更健壮可靠的代码

记住,在JS的世界里,信任但要验证——特别是对数据的类型!