前言
就我们所知, js 这门语言里数据类型分两种 -- 原始类型和引用类型
其中原始类型有number string boolean undefined null Symbol Bigint ,常见的引用类型有 Object Array Function 等等,然而在写代码的时候会用到加法运算,我们把目光移到下面这串代码:
function add(x, y) {
return x + y
}
console.log(add('1', []))
我们正常理解加法不就应该两个数字相加嘛,但是难免会有头铁挑刺的人,他偏不按套路出牌,就像我们的阿萌,他就说他偏不输入纯数字,他就是要一个放字符串一个放数组,很明显没办法输出正常结果,那么这个时候你会和阿萌进行争论,问他为什么不按套路出牌,他直接反问一句:我能输入并且执行这串代码咋就叫不按套路出牌呢?你自己不把这个代码设计好还怪我头上来了。也对吼,我们给他优化优化,设计成只有我想要的数据类型输入才能正常运行,这就得引入今天的主题 -- 数据类型的判断 ,只要我们设置一段能判断数据类型的代码加在里面,只有输入正确的数据类型才给阿萌执行后面的代码,否则直接给他显示 false ,让他无可挑剔。
在 js 里面有四种办法可以判断数据类型,在这里我就不过多废话,直接上干货
一、typeof
我们先从最简单的原始类型开始
let n = 123
let s = 'hello'
let f = true
let u = undefined
let sy = Symbol(1)
let big = 123123123n
console.log(typeof n);
console.log(typeof(s));
console.log(typeof f);
console.log(typeof u);
console.log(typeof sy);
console.log(typeof big);
首先我们用 typeof 是直接放到 console.log() 里面使用,其中 typeof 后面可以直接接变量声明,也可在后面加 () ,把变量声明放在 () 里面,就像第 9 行代码一样,他们的效果相同,紧接着我们看向输出结果发现分毫不差,全部都判断正确,但这时候阿萌又了跳出来说:看你这上面明显少了一个 null 的数据类型,是不是给漏了,诶说的好,但是别急,我也没说到这里原始类型就结束了啊,主角不都是压轴出场的嘛,我们来看看 null 是否有不同之处
let nu = null
console.log(typeof(nu));
我们用 typeof 判断 null 类型,它直接输出 object ,很明显没对上,所以我们可以得出结论 typeof 可以判断出了 null 以外的所有原始类型
接下来就该轮到引用类型了
let arr = []
let obj = {}
let fn = function() {}
let date = new Date()
console.log(typeof(arr));
console.log(typeof(obj));
console.log(typeof(date));
console.log(typeof(fn));
我们先介绍一下 Date 类型,它也是一种引用类型,只不过不太常用,我们见的比较多的都是 Object Array Function这三个,于是我们派出 Date 来代表其他不常用的引用数据类型,回归主题,通过上面输出结果我们可以发现 用 typeof 判断引用类型时,只有 Function 类型返回的是 function,其他的都是 object 。在这里不得不提一嘴:typeof 会通过将值转换为二进制,然后判断前三位是否为 0 来判断是否为对象,所有的引用类型的前三位都是 0,而null转为二进制全是 0 ,那么一切都说得通了, null 是因为这样才被判断为了对象。
二、instanceof
它与 typeof 效果可以说完全相反,typeof 可以判断除了 null 以外的所有原始类型,instanceof 则是可以判断出引用类型的具体类型
let n = 123
let s = 'hello'
let f = true
let u = undefined
let nu = null
let sy = Symbol(1)
let big = 123123123n
let arr = []
let obj = {}
let fn = function() {}
let date = new Date()
console.log(arr instanceof Array);
console.log(obj instanceof Object);
console.log(date instanceof Date);
console.log(fn instanceof Function);
console.log(n instanceof Number);
console.log(s instanceof String);
console.log(f instanceof Boolean);
console.log(sy instanceof Symbol);
console.log(big instanceof BigInt);
我们可以看到,如果是引用类型正确会返还一个
true ,反之原始类型则是返还 false ,这个时候阿萌又要跳出来讲了,你这不是少了两个原始类型吗?没错,undefined、null 比较特殊,如果用 instanceof 就会直接报错。这是因为 undefined 和 null 根本没有首字母大写的 Undefined 和 Null 。那么接下来我们再看一份代码:
let arr = []
let fn = function() {}
let date = new Date()
let n = 123
console.log(fn instanceof Object);
console.log(arr instanceof Object);
console.log(date instanceof Object);
console.log(n instanceof Object);
这时候阿萌抢答说:万物皆对象,所以当你去判断数组是否属于对象的时候会显示
true 。说的没错,但是仔细看你会发现,为啥 Number 返回的是 false ,其实“万物皆对象”只是开发者的口语化比喻,并非语言规范里的正式定义。在 ECMAScript 规范里,值被明确划分为两种数据类型:
-
原始类型
包括
Undefined、Null、Boolean、Number、String、Symbol、BigInt。它们没有内部插槽 Prototype,也不是对象,因此instanceof第一步就判定“左操作数非对象”,直接返回false。 -
对象类型
指所有具有内部插槽 Prototype 的值(数组、函数、日期、普通对象等)。它们才参与
instanceof的原型链查找,只要链上出现右侧构造函数的prototype,就返回true。
所以,规范层面“原始值 ≠ 对象”,instanceof 的行为完全符合这一划分;“万物皆对象”只是语义包装带来的错觉,并不能凌驾于语言类型系统之上。具体来说,instanceof 的工作原理依赖于对象的原型链,instanceof 用于检测构造函数的 prototype 属性是否存在于某个实例对象的隐式原型链上。对于所有引用类型(即通过 new 调用的构造函数创建的实例),只要其原型链上存在 Object.prototype instanceof Object 的求值结果即为 true。对于原始类型值,由于它们不是对象,不具备内部插槽 Prototype ,因此在执行 instanceof Object 时,算法第一步即发现左操作数不是对象,直接返回 false ,而非报错。
三、Object.prototype.toString.call()
这时候阿萌跳出来说,上面两个方法要么是只能判断原始类型,要么是只能判断引用类型,这还没什么,但是上面两种办法都有个共性,就是无法判断 null 类型。别急,现在的 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 = 123123123n
let arr = []
let obj = {}
let fn = function () { }
let date = new Date()
console.log(Object.prototype.toString.call(n));
console.log(Object.prototype.toString.call(s));
console.log(Object.prototype.toString.call(f));
console.log(Object.prototype.toString.call(u));
console.log(Object.prototype.toString.call(nu));
console.log(Object.prototype.toString.call(sy));
console.log(Object.prototype.toString.call(big));
console.log(Object.prototype.toString.call(arr));
console.log(Object.prototype.toString.call(obj));
console.log(Object.prototype.toString.call(fn));
console.log(Object.prototype.toString.call(date));
没错,
Object.prototype.toString.call() 可以判断出所有类型,但是阿萌又说,这怎么还带了 object 前缀呀,好问题,因为它返回一个字符串,这个字符串由 '[object ' + class + '] 组成,这时候不得不提出一个新
用法 -- slice()
let s = 'hello'
console.log(Object.prototype.toString.call(s).slice(8, -1));
slice(8, -1) 是指直接截取字符串第 8 到 -1(倒数第一个),并且左闭右开,取左不取右,这正好是里面的 class 。它和 splice 长得像,但是用法不同,splice是数组的方法,用来删除数组中的元素,返回的是删除的元素,在原数组上进行修改。(splice(8, 1) 代表删除 8 号位上 1 个值)接着阿萌又又又跳出来说:用 slice(8, 14) 和 slice(8, -1) 的效果一样,为啥不用前者。在这里用后者不用前者,肯定是有他的道理,来一串代码你就知道为什么
let u = undefined
console.log(Object.prototype.toString.call(u).slice(8, 14));
如上所示,每种 class 的所表示的类型都不一样,字符长度也不一样,但是你用 -1 ,它的最后一个字符一定是 ] ,所以统一用 slice(8, -1) 。Object.prototype.toString.call() 这可以说是最全面的方法,但有时候杀鸡焉用牛刀,其他方法也能用,当然你硬要用也没问题。
四、Array.isArray()
这个最后一种判断方法,也是最单调的方法,它只能用于判断数组,不能用于判断其他类型,如下所示
let arr = []
let n = 123
console.log(Array.isArray(n));
console.log(Array.isArray(arr));
结语
文章借“阿萌”不断挑刺的视角,把 JS 数据类型判断的四种武器串成一条故事线:
typeof——快但埋坑:除null外的原始类型一眼认出,引用类型只剩function独苗。instanceof——原型链侦探:精准锁定引用类型的“户口”,却拿原始值与null、undefined没辙。Object.prototype.toString.call()——终极判官:不论原始还是引用,统统一句slice(8, -1)现原形,真正的“杀鸡亦可用牛刀”。Array.isArray()——专职岗哨:只认数组,简单直接,其余类型一律免谈。 读完即可按需出牌:快速校验用typeof,认准对象用instanceof,追求万无一失直接toString,只想问“你是不是数组”就isArray。如此,任阿萌再挑刺,也能优雅堵回去。
在此感谢好基友阿萌的特别出场😁😁🙇🏻