JavaScript类型判断深度解析:从基础到精通
qiyuwang 2024-11-17 15:10 19 浏览 0 评论
JavaScript 语言具有多种数据类型,它们可以大致分为两大类:基本数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)。
一、数据类型
基本数据类型(Primitive Data Types) 包括:
- Undefined: 表示变量已被声明但未被初始化时的值。
- Null: 代表一个刻意的空值或缺失的值。
- Boolean: 只有两个值,true 或 false。
- Number: 用于表示整数和浮点数,包括Infinity、-Infinity和NaN。
- String: 用于表示文本,由零个或多个字符组成。
- Symbol: ES6 引入的新类型,表示独一无二的、不可变的数据类型,主要用于对象的属性键。
- BigInt: ES10 引入,用于表示任意大小的整数。
引用数据类型(Reference Data Types) 包括:
- Object: 一种复杂数据结构,可以包含多个键值对,包括但不限于普通对象、数组、函数等。
- Array: 特殊类型的对象,用于存储有序的元素集合。
- Function: 在JavaScript中,函数也是对象,可以作为值传递,拥有方法和属性
上面的数据类型如果说有不熟悉的,那一般是Symbol和BigInt,下面我简要说一下:
- Symbol:
最重要的特征就是唯一性,例如:
let a = Symbol(1)
let b = Symbol(1)
console.log(a === b) // false
用Symbol() 返回的东西,具有唯一性。
- BigInt:
Number 类型的安全整数范围(-2^53 到 2^53),超出这个范围的数进行计算会出现精度丢失的问题,算不准确,于是就出现了BigInt.
特点
- 创建: BigInt 可以通过在整数末尾添加 n 来创建,例如 123n。你也可以使用 BigInt() 函数将字符串转换为 BigInt,如 BigInt("123")。
- 运算: BigInt 和 Number 类型在进行算术运算时需要特别注意类型匹配。两个 BigInt 类型可以直接进行加减乘除等运算,但 BigInt 和 Number 直接运算会导致错误,需要先将 Number 转换为 BigInt。
- 比较: BigInt 和 Number 之间可以进行宽松的相等性比较(==),但严格相等性比较(===)会因为类型不同而返回 false。严格比较时,需要确保类型一致。
- 不支持: BigInt 不支持一元运算符 ++ 和 --,也不适用于Math对象的方法,以及不能用于某些JavaScript原生对象的属性,比如数组的长度。
- 字符串转换: BigInt 转换为字符串时,会保持其完整的数值,不会发生精度丢失。
示例
// 创建 BigInt
const largeNum = 1234567890123456789012345678901234567890n;
// 运算
const anotherLargeNum = 9876543210987654321098765432109876543210n;
const sum = largeNum + anotherLargeNum;
// 比较
console.log(largeNum === BigInt('1234567890123456789012345678901234567890')); // true
console.log(largeNum == 1234567890123456789012345678901234567890); // true, 松散比较
console.log(largeNum === 1234567890123456789012345678901234567890); // false, 严格比较类型不同
// 字符串转换
console.log(largeNum.toString()); // '1234567890123456789012345678901234567890'
二、类型判断时会产生的疑问
1.typeof()类型判断
console.log(typeof (null));//object
console.log(typeof (undefined));//undefined
console.log(typeof (true));//boolean
console.log(typeof (20));//number
console.log(typeof ("abc"));//string
console.log(typeof (Symbol()));//symbol
console.log(typeof (34n));//bigint
console.log(typeof ([]));//object
console.log(typeof ({}));//object
console.log(typeof (function () { }));//function
我们看上面的代码会产生两个疑问:
为什么对null的类型判断为object?
这个行为实际上是JavaScript设计初期的一个决策,后来成为了语言的一部分,被视为一个历史遗留问题。在JavaScript的最初设计中,类型信息是通过值的内部表示来区分的,特别是通过值的头部比特位。对于当时的实现来说,null的内部二进制表示是全零,这与对象类型在内存中的某些标记模式相吻合(判断其二进制前三位是否为0,是则为object,否则为原始类型),尤其是当引擎检查值的头部比特以快速区分基本类型和引用类型时,全零可能被错误地解释为了一个空对象的标记。至于后来为什么不改,则是因为大量的企业已经用JavaScript写了大量的项目,改动后,全部项目都会报错,基于这种考虑就没动。因此在以后判断类型是否为object时,需要将null排除。
为什么单独function判断类型为function,而非object?
在JavaScript中,函数(Function)是一种特殊的对象,这意味着它本质上继承了对象的特性,可以拥有属性和方法。然而,出于对语言设计和实用性考虑,typeof操作符特意将函数类型区分对待,当应用于函数时,它返回的是"function"而不是"object"。
2. instanceof类型判断
在JavaScript中,instanceof 是一个操作符,用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。它的使用方式与Java中的instanceof相似,但概念上更符合JavaScript的原型继承模型。其基本语法如下:
object instanceof Constructor
- object:需要检查的对象。
- Constructor:一个构造函数或者函数对象。
如果object是通过Constructor或其任意父类(通过原型链)构造的,那么instanceof操作符返回true;否则,返回false。
例如:
function Animal() {}
function Dog() {}
Dog.prototype = new Animal();
let myDog = new Dog();
console.log(myDog instanceof Dog); // 输出: true
console.log(myDog instanceof Animal); // 输出: true
console.log(myDog instanceof Object); // 输出: true,因为所有对象都最终继承自Object
在这个例子中,myDog对象是通过Dog构造函数创建的,而Dog的原型链上包含了Animal,因此myDog既是Dog的实例,也是Animal的实例。同时,由于JavaScript中所有对象都继承自Object,所以myDog也是Object的实例。
原始类型(如string、number、boolean、null、undefined、symbol、bigint)不是对象,因此不能直接使用instanceof来判断这些类型。如果你尝试对原始类型值使用instanceof,它们会被临时转换为对应的包装对象(如new String()、new Number()、new Boolean()),然后再进行检查,但这通常不是你想要的行为,并且对于null和undefined这样的值,这样做会直接导致错误。
例如:
let str = "some text";
console.log(str instanceof String); // 可能意外地输出: false,因为字符串不是String对象的实例
// 实际上,"some text" 在进行 instanceof 检查前会被转换为 String("some text"),但这是临时的包装对象,检查后即被销毁。
let num = 2;
console.log(num instanceof Number); // 同样可能输出: false
let bool = true;
console.log(bool instanceof Boolean); // 输出: false
console.log(null instanceof Object); // 抛出 TypeError: null is not an object (evaluating 'null instanceof Object')
console.log(undefined instanceof Object); // 抛出 TypeError: undefined is not an object (evaluating 'undefined instanceof Object')
面试题补充:请写出instanceof的判断原理。
如果面试官出这种题,那对你算是非常温柔了。
function myinstanceof(object, constructor) {
// 当对象不为null时进入循环,因为null没有__proto__
while (object !== null) {
// 如果对象的原型等于构造函数的prototype属性,说明该对象是构造函数的实例
if (object.__proto__ === constructor.prototype) {
return true;
} else {
// 如果当前对象不是实例,继续向上查找其原型链
object = object.__proto__;
}
}
// 遍历完原型链都没有找到匹配,说明不是该构造函数的实例
return false;
}
console.log(myinstanceof({}, Object)); // true
console.log(myinstanceof({}, Array)); // false
3. Object.prototype.toString.call( )
Object.prototype.toString.call() 是JavaScript中一个强大的方法,用于获取任何值的类型信息。这个方法能够返回一个表示该值的字符串,这个字符串格式通常为"[object Type]",其中Type是JavaScript中的类型名称。相比于前两种判断存在的瑕疵和不准确,这种更为完美和准确。
console.log(Object.prototype.toString.call({})); // "[object Object]"
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(new Date)); // "[object Date]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
......
......
......
在面试时,很多大厂面试官会为难你,问:有什么办法可以判断类型?
你回答:Object.prototype.toString.call( ),
他又问你为什么这个方法可以判断类型?
要回答好这个问题,就需要我们对Object.prototype.toString.call( )有着更为深入的理解:
这个方法要分两部分理解:Object.prototype.toString和call()
- Object.prototype.toString
先来看官方文档上写的Object.prototype.toString
翻译:
调用tostring方法时,会执行以下步骤:
1.如果该值未定义,则返回“[object Undefined]”
2.如果this值为null,则返回“[object Null]”
3.让o成为调用To Obiect的结果,将this值作为参数传递。(将 o 作为 To Object(this) 的执行结果)
4.定义class为o的内部属性 [[Class]] 的值
5.返回String值,该值是由三个String“[object”、class 和 “]” 连接起来的结果。
你可以将以上步骤理解为以下步骤:
1.检查值是否为undefined:如果调用toString的方法的对象是undefined,则返回"[object Undefined]"。
2.检查值是否为null:如果该值是null,则返回"[object Null]"。
3.转换为对象(To Object操作):对于非null和非undefined的值,首先通过抽象操作To Object将其转换为对象(如果还不是对象)。这意味着原始值(如数字、字符串等)会先转换为它们的包装对象,然后继续后续步骤。
4.获取内部属性[[Class]]:获取转换后的对象的内部属性[[Class]]的值。这个属性由JavaScript引擎维护,代表了对象的类型信息,比如"Array"、"Date"、"Object"等。
5.构造并返回结果字符串:最后,将字符串"[object ", class的值,以及"]"拼接起来,形成并返回最终的类型字符串,如"[object Array]"、"[object Date]"等。
然而用Object.prototype.toString()远远不足以有效的判断类型,尽管它很强大:
Object.prototype.toString()判断类型时,通常不会按照预期工作,尤其是当直接在原始值(如字符串、数字、布尔)上尝试时,因为这样调用的this并没有绑定到你想要检查的对象上。
其原因在于第三个步骤To Obiect,官方文档对此如下描述:
它会new一个对象,而不是单纯的字面量,此时其this指向发生改变,其内部属性[[class]]为指向对象的内部属性object。
因此,需要call()的帮助改变其this指向:
- call
不了解call的,我简单举个例子来说明一下效果:
var object = {
a: 11
}
function foo() {
console.log(this.a)
}
foo.call(obj) // 11
想通过func函数输出1,可以通过call方法将func里的this指向object。
我们可以尝试模拟call方法的行为,写如下代码:
var object = {
a: 11
};
function foo() {
console.log(this.a);
}
Function.prototype.mycall = function(context) {
// 检查调用mycall的是否为一个函数
if (typeof this !== 'function') {
throw new TypeError(this + ' is not a function');
}
// 使用Symbol来避免属性名冲突
const fn = Symbol('key');
// 将当前函数(this指向的func)赋值给context的一个唯一属性
context[fn] = this;
// 调用这个新添加的函数,此时this会被隐式绑定到context上
context[fn]();
// 删除临时添加的属性,以清理环境
delete context[fn];
};
foo.mycall(object); // 输出: 11
核心原理可以概括为: call方法通过在指定的context对象上临时引用并调用目标函数,实现了对该函数内部this的隐式绑定,从而使得函数能够在预期的上下文中执行。
具体来说:
- 临时绑定:它本质上是在context对象上创建一个属性(通常使用一个不易冲突的属性名,如使用Symbol),并将目标函数赋值给这个属性。(Symbol作用在于,防止别人调用你写的方法时,用同名的变量名)
- 调用函数:接着,通过context上的这个属性间接调用目标函数。由于是通过对象属性的方式来调用的,JavaScript的函数调用规则决定了此时函数内的this将绑定到该对象(即context)上。
- 清理:为了不污染context对象,调用结束后通常还会删除之前添加的临时属性,即清除本来就不存在context里的属性。
看完这两部分的解释,再来做一个总结:
使用Object.prototype.toString.call()时,当参数为Boolean,Number和String类型,call()先将这个原始值转换为其对应的包装对象,即new String('11'),然后再调用Object.prototype.toString方法,当执行到第三步to object时,发现参数为对象,则将对象赋给变量o。在这个过程中this指向因为call的纠正作用没有发生改变,因此,其内部属性[[class]]没有发生改变。
加上了call,你可以理解为以下情况:
Object.prototype.toString.call('11'); // 输出: "[object String]"
Object.prototype.toString.call(new String('11')); // 输出同样为: "[object String]"
写出完整步骤: 当执行Object.prototype.toString.call('11')时,其内部过程大致如下:
- 字符串字面量'11'作为call的第一个参数,使得toString方法内部的this指向了一个临时创建的String对象(即new String('1'))。
- 该方法检查this不是null或undefined,继续执行。
- 将这个临时的字符串对象视为操作对象O。
- 从O中获取其内部属性[[Class]],得到值"String"。
- 组合并返回字符串"[object String]",表示这是一个字符串类型的对象。
4.Array.isArray(x)
Array.isArray(x) 是JavaScript的一个内建函数,用于检测x是否为一个数组。这个方法提供了最直接和可靠的方式来判断一个变量是否是数组类型,相比使用instanceof或typeof等方法更准确,因为它不会受到不同全局执行环境(如iframe、Web Workers)中Array构造函数不同的影响。
使用示例:
let arr = [1, 2, 3];
let notArr = "I am not an array";
console.log(Array.isArray(arr)); // 输出: true
console.log(Array.isArray(notArr)); // 输出: false
这个函数非常有用,尤其是在处理可能是多种类型输入的动态数据时,能够确保你正确地识别并处理数组类型的数据。
三、结语:
在JavaScript的世界里,准确无误地判断数据类型是编写健壮、可维护代码的基础。本文从基础出发,系统梳理了JavaScript的各大数据类型,重点解析了在类型判断时常见的疑惑与误区,尤其深入探讨了typeof、instanceof以及Object.prototype.toString.call()这三种类型判断方法的原理与实践,最后还提到了Array.isArray()这一专门用于数组类型判断的便捷工具。
通过本篇内容的学习,你不仅掌握了每种判断方法的适用场景与限制,还理解了如何利用Object.prototype.toString.call()这一终极武器来实现精确无误的类型识别。记住,每种方法都有其独特的价值和潜在的陷阱,合理选择才能在实战中游刃有余。
相关推荐
- # 安装打开 ubuntu-22.04.3-LTS 报错 解决方案
-
#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...
- 利用阿里云镜像在ubuntu上安装Docker
-
简介:...
- 如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本
-
UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...
- Ubuntu 16.10内部代号确认为Yakkety Yak
-
在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...
- 如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)
-
在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
-
IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...
- 如何查看Linux的IP地址(如何查看Linux的ip地址)
-
本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...
- 怎么看电脑系统?(怎么看电脑系统配置)
-
要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...
- 如何查询 Linux 内核版本?这些命令一定要会!
-
Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...
- 深度剖析:Linux下查看系统版本与CPU架构
-
在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...
- 504 错误代码解析与应对策略(504错误咋解决)
-
在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...
- 猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录
-
10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...
- 域名解析的原理是什么?域名解析的流程是怎样的?
-
域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...
- Linux无法解析域名的解决办法(linux 不能解析域名)
-
如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。 此时,需要手动修改配置文件: /etc/resolv.conf 将域名解析服务器手动添加到配置文件中 该文件是DNS域名解...
- 域名劫持是什么?(域名劫持是什么)
-
域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- # 安装打开 ubuntu-22.04.3-LTS 报错 解决方案
- 利用阿里云镜像在ubuntu上安装Docker
- 如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本
- Ubuntu 16.10内部代号确认为Yakkety Yak
- 如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
- 如何查看Linux的IP地址(如何查看Linux的ip地址)
- 怎么看电脑系统?(怎么看电脑系统配置)
- 如何查询 Linux 内核版本?这些命令一定要会!
- 深度剖析:Linux下查看系统版本与CPU架构
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)