JavaScript类型大侦探:一文搞懂typeof、instanceof和toString()那些事儿

1,406 阅读5分钟

在JavaScript的世界里,数据类型扮演着至关重要的角色,它们决定了数据如何被处理和存储。JavaScript作为一种动态类型语言,提供了多种数据类型,主要分为两大类:原始类型(Primitive Types)和引用类型(Reference Types)。正确地识别和理解这些类型对于编写高效、健壮的代码至关重要。本文将深入探讨JavaScript中的类型判断方法,包括typeof操作符、instanceof关键字以及Object.prototype.toString()方法,并简要提及Array.isArray()作为特定引用类型的判断工具。

原始类型与引用类型存储

JavaScript引擎(如V8)对不同类型的值采取了不同的存储策略。原始类型,如数字、字符串、布尔值、undefinednullSymbolBigInt(ES6引入),由于其值相对简单且占用空间小,直接存储在调用栈中。这使得访问速度较快,且不易引发栈溢出问题。

相比之下,复杂或引用类型,如对象(包括数组、函数等),由于可能包含大量数据或复杂结构,被存储在堆内存中,而栈中仅保存指向堆中实际数据的引用(即地址)。这样的设计既节省了栈空间,又支持了大数据结构的灵活管理,但同时也要求开发者注意引用类型可能导致的循环引用和内存泄漏问题。

typeof操作符的局限

typeof是JavaScript中最基本的类型检测手段,它可以准确判断出除null以外的所有原始类型,但对于引用类型则显得力不从心——除了function,它会将所有其他引用类型(数组、对象等)一概报告为'object'

//基本类型
let str='Hello'
let num=123
let flag=false
let un=undefined
let nu=null

console.log(typeof(str))//string
console.log(typeof(num))//number
console.log(typeof(flag))//boolean
console.log(typeof(un))//undefined
console.log(typeof(nu))//object

//引用类型
let obj={}
let arr=[]
let fn=function(){}
let date=new Date()

console.log(typeof(obj))//object
console.log(typeof(arr))//object
console.log(typeof(fn))//function
console.log(typeof(date))//object

typeof的判断原理是:将值转换为二进制后,看其前三位是不是0,所有的引用类型(除了function)的二进制前三位都是0,null的二进制全部是0。因此,在需要区分具体引用类型时,typeof并不是最佳选择。

instanceof关键字的使用

当涉及到具体类或构造函数的实例判断时,instanceof关键字就显得尤为重要。它通过检查一个对象在其原型链上是否能找到某个构造函数的prototype属性,从而判断该对象是否属于某个特定的类或由某个构造函数创建。这种方法特别适用于复杂的面向对象编程场景,能够准确识别对象的继承关系。

//原始类型
let str='Hello'
let num=123
let flag=false
let un=undefined
let nu=null

console.log(str instanceof String)//false
console.log(num instanceof Number)//false
console.log(flag instanceof Boolean)//false
// console.log(un instanceof undefined)//无法判断,因为undefined和null无法检测
// console.log(nu instanceof null)//false

//引用类型
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(arr instanceof Date)//false

然而,它也有限制,即只能用于判断引用类型,对于原始类型无能为力。

手搓instanceof

function myinstanceof(L, R) {
    while (L !== null) {
    //通过原型链进行判断
        if (L.__proto__ = R.prototype) {
            return true
        }
        L = L.__proto__
    }
    return false
}

var arr = {}
console.log(myinstanceof(arr, Object))//true

Object.prototype.toString()的全面性

对象的toString()

  • Object.prototype.toString(x)
  1. 如果toString接收的值是undefined,则返回“[object Undefined]"
  2. 如果toString接收的值是null,则返回“[object Null]"
  3. 调用ToObject(x)将x转为对象,此时得到的对象内部一定拥有一个属性[[Class]],而该属性[[Class]]的值就是x的类型
  4. 设class是[[Class]]的值
  5. 返回由"[object "和class和"]"连接而成的字符串
let a={}
let b=[]
let c='hello'

console.log(Object.prototype.toString(a));//[object Object]
console.log(Object.prototype.toString.call(b)); //[object Array]
console.log(Object.prototype.toString.call(c));//[object String]

为何bc使用Object.prototype.toString()时为何同时使用了call()

直接调用数组和字符串实例的toString方法可能会受到对象原型上toString方法被覆盖的影响。而使用Object.prototype.toString.call()确保了调用的是最原始、未被修改的toString方法,提高了代码的稳定性和可靠性。

数组的toString()

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

数组的toString()方法是一个非常实用且内置的功能,它能够将数组中的所有元素转换成字符串形式,并用逗号,连接这些字符串,最后返回一个由这些字符串组成的单个字符串。这个过程简单直观,非常适合于需要将数组内容以易于阅读的文本格式展示的场景。

其它的toString()

//Number
let num = 123;
console.log(num.toString()); // 输出 "123"

//String
let str = "Hello";
console.log(str.toString()); // 输出 "Hello"

//Boolean
console.log(true.toString()); // 输出 "true"
console.log(false.toString()); // 输出 "false"

//函数function
let func = function() { return "Hello"; };
console.log(func.toString()); // 输出 "function (){ return "Hello"; }"

直接将值修改成字符串字面量

Array.isArray()的特异性

对于数组这种特定的引用类型,JavaScript ES5引入了Array.isArray()方法,它专门用于判断一个值是否为数组。虽然功能单一,但它提供了一种直接且明确的方式来识别数组,避免了使用更通用的instanceofObject.prototype.toString()所带来的潜在复杂性。

总结

在JavaScript开发中,正确地识别和处理不同类型的数据是基础且关键的技能。typeofinstanceofObject.prototype.toString()以及Array.isArray()各有所长,理解它们的适用场景和限制,能够帮助开发者更加高效和准确地进行类型判断,从而写出更加健壮、易于维护的代码。在面对复杂的数据结构和类型检测需求时,综合运用这些工具,将使你的JavaScript之旅更加顺畅。