js类型判断方法及原理剖析

844 阅读6分钟

引言

在JavaScript编程中,类型判断是我们经常会遇到的问题。对于初学者来说,有时候可能会困惑于如何准确地判断一个变量的类型。今天,我们就来揭开JavaScript中常用的几种类型判断方法及其原理,帮助大家更好地理解和运用。

1. typeof 操作符

首先,让我们来介绍一下最常用的类型判断方法之一:typeof操作符。通过typeof操作符,我们可以得知一个变量是什么基本类型,比如是字符串、数字、布尔值、函数等。它的语法是:typeof 变量名。值得注意的是,typeof操作符返回的结果是一个字符串,表示被检测变量的数据类型。

直接上几个代码示例:


let s = '123'   // string
let n = 123    // number
let f = true   // boolean
let u = undefined   // undefined
let nu = null    // null
let sy = Symbol(123)   // Symbol
let big = 1234n    // BigInt

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


console.log(typeof(s));  // string
console.log(typeof(n));  // number
console.log(typeof(f));  // boolean
console.log(typeof(u));  // undefined

console.log(typeof(nu));  // object

console.log(typeof(sy));  // symbol
console.log(typeof(big));  // bigint


console.log(typeof(obj));  // object
console.log(typeof(arr));  // object

console.log(typeof(fn));  // function

console.log(typeof(date));  // object

判定规则

  1. typeof 可以判断除null以外的 所有基本数据类型
  2. 除了function,其它所有引用数据类型,都是object

内部原理

相信你一定有两个疑问

  1. 为什么function返回的不是object?
  2. 为什么null会返回object?

首先我们来解答第一个问题,因为typeof的内部原理是:

  1. JS引擎为每种数据类型分配了特定的内部类型标识。这些标识通常与ECMAScript规范中定义的类型相对应,包括Undefined, Null, Boolean, Number, String, Symbol, BigInt, Object, 以及特殊的Function
  2. typeof在接受参数后,会将其转化为二进制,基本数据类型前3位一定不是0,引用数据类型前3位为0
  3. 不是0进行进一步的转换,得到具体的类型
  4. 前3位是0,如果具有[[Call]](调用),则为Function,否则为Object

所以因为function具有[[Call]](调用),所以返回的不是object。

那么,第二个问题。其实这是一个bug,因为js官方在设计这门语言的时候将null的二进制编码设置成了全零,所以会造成返回Object

所以,如果要判断一个变量是不是object,需要加上不等于null的特判

function isObject(obj) {
    return typeof obj === 'object' && obj !== null;
}

2. instanceof 操作符

除了typeof操作符,我们还可以使用另一种方法进行类型判断,那就是instanceof操作符。instanceof操作符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。如果在原型链上找到了对应的构造函数,则返回true,否则返回false。

let s = '123'   // string
let n = 123    // number
let f = true   // boolean
let u = undefined   // undefined
let nu = null    // null
let sy = Symbol(123)   // Symbol
let big = 1234n    // BigInt

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

console.log(obj instanceof Object); //true
console.log(arr instanceof Array); //true
console.log(fn instanceof Function);//true
console.log(date instanceof Date);//true

console.log(s instanceof String);//false
console.log(n instanceof Number);//false


console.log(arr instanceof Object);//true

判断规则

  1. 只能判断引用数据类型
  2. 只会返回truefalse,检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

内部原理

其实就是通过原型链查找来判断类型。

我们可以通过手写一个myinstanceof来更好的理解它的原理。

function myinstanceof(a, b) {
    while (a) {
        if (a.__proto__ === b.prototype) return true;
        a = a.__proto__;
    }
    return false;
}

console.log(myinstanceof([], Array));//true
console.log(myinstanceof([], Object));//true

其实,就是不断查找a的隐式原型的隐式原型...(不断往下)是否等于的b的显示原型,如果等于则返回true。

如果您对JS原型有困惑,建议您查看我之前写的文章《干货分享(三)——深入理解JS原型和原型链》,这可以帮助您更深入理解和理顺JS中原型和原型链的概念。

3. Object.prototype.toString.call() 方法:

再来介绍一个更加严谨和通用的类型判断方法:Object.prototype.toString.call()。它可以准确地判断 JavaScript 中的数据类型,甚至包括了对于 Object 的具体子类型的判断。例如:

Object.prototype.toString.call(42); // "[object Number]"
Object.prototype.toString.call("hello"); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call({key: 'value'}); // "[object Object]"
Object.prototype.toString.call(function() {}); // "[object Function]"

内部原理

在了解Object.prototype.toString.call()之前我们先要了解Object.prototype.toString()call() 的原理

1 .Object.prototype.toString() 原理

在JSAnnotated ES5官方文档中的解释为:

image.png

翻译过来就是:

  1. 如果this为undifined,则返回“[object Undefined]”。
  2. 如果this为null,则返回“[object Null ]”。
  3. 设 O 为 ToObject(this) 的结果,并将此值作为参数传递。
  4. 定义一个Class,为 O 的[[ Class ]]的值。
  5. 返回由“[ object” 和 class 和 “]” 组成的字符串

而对于ToObject(this),官方解释为:

image.png

简单来说就是:

  1. 如果是Undifined或者Null,抛出一个TypeError错误
  2. 如果是Boolean、Number、String 创建对应的对象返回
  3. 如果是Object不变

2 .call(context) 核心原理

我们通过一个手写的简易版的mycall方法来了解他的原理,其实就是利用了隐式绑定,将this强行指向你指定的context。

Function.prototype.mycall = function (context) {
    // 调用我的是不是函数体,只有函数才能使用call方法
    if (typeof this !== 'function') {
        return new TypeError(this + 'is not a function');
    }

    // 利用this隐式绑定规则
    const fn = Symbol('key') // 防止使用时冲突
    context[fn] = this; // 让对象拥有该函数
    context[fn](); // 触发 隐式绑定 
    // 删除刚刚添加的fn属性
    delete context[fn];
}

如果您对JS中this指向有困惑,建议您查看我之前写的文章《深入探究:一篇文章搞懂JS this指向》,这将帮助您更好地理解this在JS中的指向规则。

3. Object.prototype.toString.call() 原理

现在你已经知道了Object.prototype.toString()call() 的原理,但其实如果你单独使用Object.prototype.toString(),只要不输入undifined,返回的都是 [object Object] 。

image.png

这是因为Object.prototype.toString()单独使用时,会触发隐式绑定规则,使得this指向Object.prototype,也就是一个object。所以按照它自己的规则,规则1,2,3都不会对它产生任何影响,规则4就会取到它自己的[[ Class ]],从而输出[object Object]。

而使用call,会像this强行指向你传入的参数,以Number类型为例:

Object.prototype.toString()的规则3就会返回一个Number对象,从而返回Number对象的[[ Class ]],而不是object的[[ Class ]],所以能返回正确的结果[object Number]

image.png

4. Array.isArray(x)

最后一个我们要介绍的是用于判断数组类型的方法:Array.isArray()。它在判断一个变量是否为数组时非常方便,如果是数组则返回 true,否则返回 false。让我们来看一个例子:

Array.isArray([1, 2, 3]); // true
Array.isArray("hello"); // false

只能用于数组检测

内部原理

在JS引擎内部,Array.isArray()函数会检查传入值的[[Class]]内部属性,这是一个内部元数据属性,用于标识对象的类型。对于数组实例,这个属性的值为"Array"Array.isArray()正是通过比较这个内部属性来判断一个值是否为数组。

结语

以上就是JavaScript中常用的几种类型判断方法及其原理介绍。希望通过本文的分享,大家能够更清晰地了解这些方法的使用及底层原理,从而在实际编程中更加灵活自如地应用。如果你觉得这篇文章有帮助或有所启发,别忘了给我一个鼓励的