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

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

JavaScript的内存布局与类型存储

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

typeof操作符的深入分析

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

javascript 复制代码
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使用的示例

js 复制代码
// 基本类型
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

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

js 复制代码
// 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方法

javascript 复制代码
/** 
 * 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()的全面性

javascript 复制代码
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()的对比

javascript 复制代码
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(): 直接将值修改成字符串字面量,也就是直接加个引号

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

js 复制代码
// 打造一个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()的独特性

javascript 复制代码
let arr = [];
console.log(Array.isArray(arr)); // true

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

结论

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

相关推荐
天天向上102413 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y29 分钟前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁36 分钟前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry36 分钟前
Fetch 笔记
前端·javascript
拾光拾趣录37 分钟前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟38 分钟前
vue3,你看setup设计详解,也是个人才
前端
Lefan42 分钟前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构
vvilkim1 小时前
Nuxt.js 全面测试指南:从单元测试到E2E测试
开发语言·javascript·ecmascript
写不出来就跑路1 小时前
基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析
前端·vue.js·ui