如何准确判断 JS 中的数据类型? instanceof 和 typeof 有什么区别?

在JavaScript开发中,35%的bug源于数据类型判断错误。当使用typeof检测数组时得到"object",用instanceof检查跨窗口对象时返回false,这些陷阱让开发者头疼不已。本文将深入解析typeof与instanceof的核心差异,揭密Object.prototype.toString.call的终极解决方案,并手把手教你实现类型判断的完美闭环。

一、typeof运算符:基础但局限

基本语法:
```javascript
console.log(typeof 42); // "number"
console.log(typeof 'text'); // "string"
```

1.1 可识别的7种基本类型

  • undefined: typeof undefined ➔ "undefined"
  • boolean: typeof true ➔ "boolean"
  • number: typeof NaN ➔ "number"(特殊值仍返回number)
  • string: typeof `template` ➔ "string"
  • symbol: typeof Symbol() ➔ "symbol"
  • bigint: typeof 10n ➔ "bigint"
  • function: typeof function(){} ➔ "function"

1.2 令人困惑的null检测

```javascript
typeof null; // "object" (历史遗留问题)
```
这个结果导致无法用typeof区分null与普通对象,需配合严格相等判断:
```javascript
const isNull = value => value === null;
```

二、instanceof运算符:原型链检测器

运行原理:
```javascript
obj instanceof Constructor
// 等价于
Constructor.prototype.isPrototypeOf(obj)
```

2.1 典型应用场景

```javascript
[] instanceof Array; // true
new Date() instanceof Date; // true
```

2.2 三大核心缺陷

  1. 跨窗口检测失效:iframe中的数组不是主窗口Array的实例
  2. 原始类型误判:1 instanceof Number ➔ false
  3. 原型污染风险:修改prototype会导致判断错误

三、终极解决方案:Object.prototype.toString.call

标准实现方案:
```javascript
function getType(obj) {
return Object.prototype.toString.call(obj)
.replace(/\[object\s(.+)\]/, "$1").toLowerCase();
}

// 测试用例
getType(null); // "null"
getType([]); // "array"
getType(/regex/); // "regexp"
```

3.1 支持识别的完整类型

返回值对应类型
[object Null]null
[object Undefined]undefined
[object Boolean]布尔值
[object Number]数值
[object String]字符串
[object Array]数组
[object Function]函数
[object Date]日期对象

四、手写实现instanceof

通过原型链递归实现:
```javascript
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
while(proto) {
if(proto === constructor.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
```

五、TypeScript中的进阶实践

5.1 类型守卫(Type Guards)

```typescript
function isString(value: unknown): value is string {
return typeof value === "string";
}

function processValue(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()); // 安全调用字符串方法
}
}
```

5.2 unknown与any的抉择

推荐方案:
```typescript
let value: unknown = "Hello";

// 安全写法
let length = (value as string).length;

// 危险写法(失去类型检查)
let length = (value as any).length;
```

总结:类型判断四层防御体系

  1. 第一层:typeof检测基本类型
  2. 第二层:=== null判断空值
  3. 第三层:Object.prototype.toString.call识别复杂类型
  4. 第四层:instanceof验证自定义对象

通过组合使用这些方法,可构建覆盖所有场景的类型判断方案。在TypeScript项目中,配合类型守卫和unknown类型,更能将类型错误扼杀在编译阶段。