你不知道的JavaScript——类型判断篇

最近我了解到了包括typeofinstanceofObject.prototype.toString()等在内的类型判断方法。我将在这里深入探究这些类型检测方法的工作原理,以及如何自定义实现instanceof逻辑,以加深对JavaScript类型系统的了解。

JavaScript的内存布局与类型存储

首先,理解JavaScript的内存布局是基础。原始类型(如字符串、数字、布尔值等)因数据量小,直接存储在调用栈中,便于快速访问和管理。而复杂类型(如对象、数组)因其潜在的复杂性和大小,被存放在堆内存中,栈中仅保存一个指向堆中数据的引用地址。这样的设计既避免了栈空间有限导致的“栈溢出”风险,又能灵活处理大数据结构。

typeof操作符的深入分析

让我们来看看下面这段例子

let str = "Hello"       // string
let num = 123           // number
let flag = false        // boolean
let un = undefined      // undefined
let nu = null           // object
let symbol = Symbol()   // symbol   
let bigInt = BigInt(123)// bigint 
let obj ={}             // object 
let arr = []            // object  
let fn = function() {}  // function
let reg = /^abc$/       // object  
let date = new Date()   // object 
let map = new Map()     // object    
let set = new Set()     // object  

后面的注释是输出的结果

typeof操作符在检测原始类型时表现良好,但对引用类型,除了函数外,它一律返回"object"。这源于typeof通过检查值的内部表示,对于非函数引用类型,其二进制表示前三位为0,导致无法精确区分具体类型。特别地,它错误地将null识别为"object",这是typeof的一个已知缺陷。
以上可以总结为三句话:

  1. 可以判断除null之外的原始类型
  2. 无法判断function之外的引用类型
  • typeof的判断原理是:将值转换为二进制后看前三位是不是0,除函数的所有引用类型的二进制前三位都是0,null转换为二进制全是0

instanceof的精髓:原型链的探索

下面为instanceof使用的示例

// 基本类型
let str = "Hello"       
let num = 123           
let flag = false        
let un = undefined      
let nu = null           
let symbol = Symbol()       
let bigInt = BigInt(123)

// console.log(str instanceof String);      // false
// console.log(num instanceof Number);      // false
// console.log(flag instanceof Boolean);    // false
// console.log(un instanceof Undefined);    // 报错
// console.log(nu instanceof Null);         // 报错


// 引用类型
let obj ={}             
let arr = []            
let fn = function() {}  
let reg = /^abc$/       
let date = new Date()   
let map = new Map()     
let set = new Set()

// console.log(obj instanceof Object);      // true    
// console.log(arr instanceof Array);       // true    
// console.log(fn instanceof Function);     // true    
// console.log(reg instanceof RegExp);      // true    
// console.log(date instanceof Date);       // true    
// console.log(map instanceof Map);         // true    
// console.log(set instanceof Set);         // true  
// console.log(arr instanceof Object);      // true

欸,发现了一个特殊的例子

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

使用instanceof把数组判断为Object返回的也是true。那这是怎么回事呢,那我们就需要了解intanceof方法的实现原理

其实啊

instanceof是通过原型链的追溯,说到原型链,不知道大家还记得我在 JavaScript原型探秘:构建对象与继承的艺术中的概述吗,实例对象的隐式原型会等于构造函数的显示原型,所以arr.__proto__=Array.prototype,并且Array是通过Object来创建的,所以Array.__proto__=Object.prototype,找到了Object所以返回true。如果没找到,会跟原型链一样层层往上找,找到null为止。
使用instanceof这个方法来判断一个对象是否为某个构造函数的实例,这对于区分复杂的引用类型非常有用,但仅限于引用类型。

手写instanceof

根据上面的描述,我们知道了instanceof方法的工作原理,那么我们来根据这些原理来仿写一个myInstanceof方法

/** 
 * while循环,直到原型为null或找到R.prototype
 * @param {Object} L 需要判断的对象
 * @param {Function} R 构造函数
 * @return {Boolean} 返回布尔值 
 */ 
function myInstanceof(L, R) {
    while(L !== null) {
        if(L.__proto__ === R.prototype) return true;
        L = L.__proto__;
    }
    return false;
}

console.log(myInstanceof(new Array(), Array));  // true
console.log(myInstanceof(new Array(), Object)); // true
console.log(myInstanceof(1, Object));          // true

instanceof通过检查对象的原型链来判断其是否为某构造函数的实例。自定义的myInstanceof函数通过迭代对象的原型链直至null,寻找是否有构造函数的原型,复现了instanceof的行为,展示了如何利用这一机制进行类型判断。也可以使用递归来实现

Object.prototype.toString()的全面性

let a = {};
let b = [];
let c = "hello";

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

Object.prototype.toString()通过直接访问对象的内部[[Class]]属性,提供了最准确的类型信息。通过.call()方法确保方法作用于正确的对象,它能够返回如"[object Array]"这样精确的类型字符串。这种方法克服了typeof的局限性,能够准确区分所有内置类型,包括数组、正则表达式等。

那么,我们可以把Object.prototype.toString()方法的实现原理总结为如下五步:

  1. 如果toString接收的值为 undefinded,则返回“ [object Undefined] ”。
  2. 如果toString接收的值为 null,则返回“ [object Null] ”。
  3. 调用ToObject(x) 将x转为对象,此时得到的对象内部一定拥有一个属性[[class]],而这个属性[[class]]的值就是x的类型。
  4. 设 class 是[[class]]的值
  5. 返回由“[ object” 和 class 和 “]”拼接的字符串

toString()与Object.prototype.toString()的对比

console.log([1, 2, 3].toString()); // "1,2,3"
console.log(({}).toString());      // "[object Object]"

标准的toString()方法在不同对象上有不同的表现:对于数组,它将数组元素转换为字符串并用逗号连接;对于普通对象,它返回"[object Object]"。而Object.prototype.toString()提供了更全面的信息,是进行类型检查时的首选。

toString方法的使用可以总结为:

  1. 对象的toString(): Object.prototype.toString
  2. 数组的toString(): 将数组中的元素用逗号的方式拼接成字符串
  3. 其他的toString(): 直接将值修改成字符串字面量,也就是直接加个引号

实现一个全面的数据类型判断方法

// 打造一个type函数,能够判断参数的类型 使用Object.prototype.toString() 方法

function type(x) {
    return Object.prototype.toString.call(x).slice(8, -1);
}

console.log(type(5));
console.log(type("hello"));

这个 type 函数通过利用 Object.prototype.toString.call() 的能力,提供了一种强大的类型检测机制,能够准确识别并返回JavaScript中各种数据类型的名称,利用数组的slice方法把需要的内容分离出来,使方法更直观

Array.isArray()的独特性

let arr = [];
console.log(Array.isArray(arr)); // true

Array.isArray()是一个专门用于检测数组的工具,它直接、明确,避免了使用instanceoftypeof可能带来的误判。值得注意的是,它只能作为构造函数调用,体现了其针对性和准确性。

结论

综上所述,JavaScript的类型检测与内存管理机制是编程中不可或缺的知识点。通过理解typeofinstanceofObject.prototype.toString()以及Array.isArray()的工作原理和适用场景,我们能够更精准地处理各种数据类型,写出更高效、健壮的代码。自定义实现如myInstanceof不仅增强了对原型链和类型判断的深入理解,也为解决特定问题提供了灵活性。

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值