【JavaScript】一篇文章,带你拿捏JS中的类型判断

0 阅读5分钟

前言

前面我们介绍了 JS 中的数据类型,今天我们就接着来聊一下 JS 中判断数据类型的方法

1. typeof

【JavaScript】✨ JavaScript 对象 & 包装对象:魔法世界大冒险!一文中,我们已经提过了,今天就再来复习一遍

1.1 JS 内存中的数据表示

JS 是一种动态的弱类型语言,JS 引擎在存储标量的值时会先将其转为一个二进制的机器码,包括类型标签和值的内容两个部分。

  • 类型标签:处在机器码的地位,用于指示值的数据类型
  • 值内容:顾名思义,用于表示数据的实际值 下面展示的是一些常见的类型标签
类型标签位数据类型
000对象
1数字
001函数
010字符串
110布尔值
111undefined
000null

1.2typeof 的原理

typeof 是 JS 原生一个一元操作符,可以判断除 null 之外的原始数据类型。用其判断原始数据类型,除了 function 以外,其它的都返回 object

当 typeof 判断一个值得数据类型时,会直接读取 JS 引擎存储在内存中的类型标签部分,并映射相应的数据类型

console.log(typeof(100))
console.log(typeof('100'))
console.log(typeof(true))
console.log(typeof(undefined))
console.log(typeof(null))
console.log(typeof({}));
console.log(typeof(Symbol()));
console.log(typeof(BigInt(100)));
//number
//string
//boolean
//undefined
//object
//object
//symbol
//bigint

1.3 typeof null === object

JS 将 null 设计为全零值,故将其转为二进制后,它的机器码为全零,当 typeof 读取它的类型标签时,读取到的为000,与对象的类型标签重叠,故错误地将其判断为object

2. instanceof

2.1 instanceof 的原理

let ls = new Set();
console.log(date instanceof Date);
//true

instanceof 是 JS 的一个内置操作符,通过检查对象的隐式原型链来判断这个对象是否属于某个特定的类或者构造函数,返回一个布尔值表示判断情况。其判断的核心机制是:查找左边对象的原型链是否存在某一项等于右边构造函数的 prototype。

//我们可以来手动实现一个简易版的instanceof
function myinstanceof(L,R) {
 L=L.__proto__
 while(true){
  if(L===R.prototype){
    return true
  }
  L=L.__proto__
 }
 return false
}

2.2 注意

因为 instanceof 是基于原型链来判断数据类型的,故其不能用来判断原始数据类型(用new创建的包装对象除外)以及Object.create()创建的对象(没有原型)

let num = 10;
let str = "foo";
let bool = true;
let nul = null;
let undef = undefined;
let sym = Symbol();
let big = BigInt(10);
let obj = {};
let date =new Date();
let ls = new Set();
let map=new Map()
console.log(num instanceof Number);
console.log(str instanceof String);
console.log(bool instanceof Boolean);
// console.log(nul instanceof null 这个不是构造函数,不能比较);
// console.log(undef instanceof undefined 这个不是构造函数,不能比较);
console.log(sym instanceof Symbol);
console.log(big instanceof BigInt);
console.log(obj instanceof Object);
console.log(date instanceof Date);
console.log(ls instanceof Set);
console.log(map instanceof Map);


3. Object.prototype.toString.call()

3.1 Object.prototype.toString的原理

在JS引擎内部,每一个对象都有一个内部属性[[class]],它是一个字符串值,用于标识对象的类型。当执行obj.prototype.toString()时,JS引擎会做这几件事

  1. 检查obj是否为undefined或者null,如果是的话,直接返回对应的字符串
  2. 检查obj是否是一个对象,如若不是,则对其进行装箱(隐式封装,将其转换为对应的封装对象)
  3. 读取对象身上的[[class]]属性
  4. 组合字符串"[object " + [[Class]] + "]"并返回
let num = 10;
let str = "foo";
let bool = true;
let nul = null;
let undef = undefined;
let sym = Symbol();
let big = BigInt(10);
let obj = {};
let date = new Date();
let ls = new Set();
let map = new Map();
let arr = [];
let func = function () {};
console.log(Object.prototype.toString.call(num));
console.log(Object.prototype.toString.call(str));
console.log(Object.prototype.toString.call(bool));
console.log(Object.prototype.toString.call(nul));
console.log(Object.prototype.toString.call(undef));
console.log(Object.prototype.toString.call(sym));
console.log(Object.prototype.toString.call(big));
console.log(Object.prototype.toString.call(obj));
console.log(Object.prototype.toString.call(date));
console.log(Object.prototype.toString.call(ls));
console.log(Object.prototype.toString.call(map));
console.log(Object.prototype.toString.call(arr));
console.log(Object.prototype.toString.call(func));
//[object Number]
//[object String]
//[object Boolean]
//[object Null]
//[object Undefined]
//[object Symbol]
//[object BigInt]
//[object Object]
//[object Date]
//[object Set]
//[object Map]
//[object Array]
//[object Function]

3.2 Object.prototype.toString配合call使用的原因

Object.prototype.toString()一定要搭配call来使用,才能准确的判断数据类型。为什么呢?让我们一起来看一下

let obj=[1,2,3]
console.log(obj.toString());
//1,2,3

前文提到Object.prototype.toString的原始功能是返回对象的内部类型信息(格式为[object Type]),而现在JS中很多内置对象都重写了这个方法用于返回一些特定的字符串。

JS中对象的方法调用遵循原型链查找规则:当调用obj.toString()时,JavaScript 引擎会先在obj自身的属性中查找toString方法,若找不到,才会沿着原型链向上查找。所以对于这个对象来说,直接调用toString()方法,是无法获取它的原始类型信息的。

那你会想,既然对象身上的toString方法不准确,那我取它的原型上找不就好了?

let obj=[1,2,3]
console.log(obj.__proto__.toString());
//

nonono,出错了哦。让我一起来分析一下上述代码。obj.__proto__等于Array.prototype,也就是说obj.__proto__.toString()实际等于Array.prototype.toString.而Array.prototype.toString已经被重写为返回的数组中的元素字符串,我们直接在原型上调用,this指向Array本身,即一个空数组,因此返回一个字符串。

因此,若要准确检测对象类型,必须要使用Object.prototype.toString.call(obj)这种方式,强制调用对象的原始方法并绑定目标对象,才能准确的判断它的数据类型

let obj=[1,2,3]
console.log(obj.toString());
console.log(obj.__proto__.toString());
console.log(Object.prototype.toString.call(obj));
//1,2,3
//
//[object Array]

4. Array.isArray()

Array.isArray() 是 JavaScript 中用于判断一个值是否为数组的标准方法不仅能准确识别数组,还能有效排除类数组对象的干扰,是开发中优先推荐的数组检测方法。

// 检测真正的数组
console.log(Array.isArray([])); // true
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray(new Array(5))); // true

// 检测类数组对象(非数组但有length属性的对象)
const arrayLike1 = { length: 3, 0: "a", 1: "b", 2: "c" }; // 类数组对象
const arrayLike2 = { length: 0 }; // 空类数组
console.log(Array.isArray(arrayLike1)); // false
console.log(Array.isArray(arrayLike2)); // false

// 函数中的arguments对象(类数组)
function testArgs() {
  console.log(Array.isArray(arguments)); // false(arguments是类数组,不是数组)
}
testArgs(1, 2, 3);