当前位置: 首页 > news >正文

JavaScript 类型判断及类型转换规则

文章目录

    • JavaScript 类型及其判断
    • 使用 typeof 判断类型
    • 使用 instanceof 判断类型
    • 使用 constructor 和 Object.prototype.toString 判断类型
    • JavaScript 类型及其转换
    • JavaScript 函数参数传递
    • cannot read property of undefined 问题解决方案
    • 分析一道网红题目
    • JavaScript 类型判断总结

在这里插入图片描述

✍创作者:全栈弄潮儿
🏡 个人主页: 全栈弄潮儿的个人主页
🏙️ 个人社区,欢迎你的加入:全栈弄潮儿的个人社区
📙 专栏地址,欢迎订阅:前端架构师之路

JavaScript 类型及其判断

JavaScript 具有七种内置数据类型,它们分别是:

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol

其中,前面五种为基本类型。第六种 object 类型又具体包含了 function、array、date 等。

对于这些类型的判断,我们常用的方法有:

  • typeof
  • instanceof
  • Object.prototype.toString
  • constructor

使用 typeof 判断类型

基本类型可以使用 typeof 来判断:

typeof 5 // "number"
typeof 'lucas' // "string"
typeof undefined // "undefined"
typeof true // "boolean"

但是也存在着一些特例,比如用 typeof 判断 null 时:

typeof null // "object"

我们再看使用 typeof 判断复杂类型时的表现:

const foo = () => 1
typeof foo // "function"const foo = {}
typeof foo // "object"const foo = []
typeof foo // "object"const foo = new Date()
typeof foo // "object"const foo = Symbol("foo") 
typeof foo // "symbol"

因此,我们可以总结出:使用 typeof 可以准确判断出除 null 以外的基本类型,以及 function 类型、symbol 类型;null 会被 typeof 判断为 object。

使用 instanceof 判断类型

再来看看 instanceof:

使用 a instanceof B 判断的是:a 是否为 B 的实例,即 a 的原型链上是否存在 B 构造函数 。因此如果我们使用:

function Person(name) {this.name = name
}
const p = new Person('lucas')p instanceof Person
// true

这里 p 是 Person 构造出来的实例。同时,顺着 p 的原型链,也能找到 Object 构造函数:

p.__proto__.__proto__ === Object.prototype

因此:

p instanceof Object// true

原型原型链的知识我们会在后续章节中介绍,这里只需要理解 instanceof 的判断原理即可。另外,一个细节需要注意:

5 instanceof Number // false

返回 false,是因为 5 是基本类型,它并不是 Number 构造函数构造出来的实例对象,如果:

new Number(5) instanceof Number // true

结果返回 true。
我们使用以下代码来模拟 instanceof 原理:

// L 表示左表达式,R 表示右表达式
const instanceofMock = (L, R) => {if (typeof L !== 'object') {return false}while (true) { if (L === null) {// 已经遍历到了最顶端return false}if (R.prototype === L.__proto__) {return true}L = L.__proto__} 
}

L 表示左表达式,R 表示右表达式,我们可以如此使用:

instanceofMock('', String)// falsefunction Person(name) {this.name = name
}
const p = new Person('lucas')instanceofMock(p, Person)// true

使用 constructor 和 Object.prototype.toString 判断类型

使用 constructor 可以查看目标的构造函数,这也可以进行类型判断,但也存在着问题,具体请看:
var foo = 5
foo.constructor
// ƒ Number() { [native code] }

var foo = ‘Lucas’
foo.constructor
// ƒ String() { [native code] }

var foo = true
foo.constructor
// ƒ Boolean() { [native code] }

var foo = []
foo.constructor
// ƒ Array() { [native code] }

var foo = {}
foo.constructor
// ƒ Object() { [native code] }

var foo = () => 1
foo.constructor
// ƒ Function() { [native code] }

var foo = new Date()
foo.constructor
// ƒ Date() { [native code] }

var foo = Symbol(“foo”)
foo.constructor
// ƒ Symbol() { [native code] }

var foo = undefined
foo.constructor
// VM257:1 Uncaught TypeError: Cannot read property ‘constructor’ of undefined
at :1:5

var foo = null
foo.constructor
// VM334:1 Uncaught TypeError: Cannot read property ‘constructor’ of null
at :1:5

我们发现对于 undefined 和 null,如果尝试读取其 constructor 属性,将会进行报错。并且 constructor 返回的是构造函数本身,一般使用它来判断类型的情况并不多见。

使用 Object.prototype.toString 判断类型,我们称之为“万能方法”,“终极方法”:
console.log(Object.prototype.toString.call(1))
// [object Number]

console.log(Object.prototype.toString.call(‘lucas’))
// [object String]

console.log(Object.prototype.toString.call(undefined))
// [object Undefined]

console.log(Object.prototype.toString.call(true))
// [object Boolean]

console.log(Object.prototype.toString.call({}))
// [object Object]

console.log(Object.prototype.toString.call([]))
// [object Array]

console.log(Object.prototype.toString.call(function(){}))
// [object Function]

console.log(Object.prototype.toString.call(null))
// [object Null]

console.log(Object.prototype.toString.call(Symbol(‘lucas’)))
// [object Symbol]

JavaScript 类型及其转换

JavaScript 的一个显著特点就是“灵活”。“灵活”的反面就是猝不及防的“坑”多,其中一个典型的例子就是被诟病的类型“隐式转换”。

MDN 这样介绍过 JavaScript 的特点:JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。

我们再来看一些基本例子,在使用加号进行运算时:

console.log(1 + '1')
// 11console.log(1 + true)
// 2console.log(1 + false)
// 1console.log(1 + undefined)
// NaNconsole.log('lucas' + true)
// lucastrue

我们发现:

当使用 + 运算符计算 string 和其他类型相加时,都会转换为 string 类型;其他情况,都会转换为 number 类型,但是 undefined 会转换为 NaN,相加结果也是 NaN
比如布尔值转换为 number 类型:true 为 1,false 为 0,因此:

console.log(1 + true)
// 2console.log(1 + false)
// 1console.log(false + false)
// 0console.log(true + true)
// 2

再看代码:

console.log({} + true)
// [object Object]true

在 + 号两侧,如果存在复杂类型,比如对象,那么这到底是怎样的一套转换规则呢?

当使用 + 运算符计算时,如果存在复杂类型,那么复杂类型将会转换为基本类型,再进行运算。

这就涉及到“对象类型转基本类型”这个过程。具体规则:对象在转换基本类型时,会调用该对象上 valueOf 或 toString 这两个方法,该方法的返回值是转换为基本类型的结果

那具体调用 valueOf 还是 toString 呢?这是 ES 规范所决定的,实际上这取决于内置的 toPrimitive 调用结果。主观上说,这个对象倾向于转换成什么,就会优先调用哪个方法。如果倾向于转换为 Number 类型,就优先调用 valueOf;如果倾向于转换为 String 类型,就只调用 toString。

valueOf 以及 toString 是可以被开发者重写的。比如:

const foo = {toString () {return 'lucas'},valueOf () {return 1}
}

我们对 foo 对象的 valueOf 以及 toString 进行了重写,这时候调用:

alert(foo)

输出:lucas。这里就涉及到“隐式转换”,在调用 alert 打印输出时,“倾向于使用 foo 对象的 toString 方法,将 foo 转为基本类型”,得以打印出结果。
然而:

console.log(1 + foo)

输出:2,这时候的隐式转换“倾向于使用 foo 对象的 valueOf 方法,将 foo 转为基本类型”,得以进行相加。

我们再全面总结一下,对于加法操作,如果加号两边都是 Number 类型,其规则为:

  • 如果 + 号两边存在 NaN,则结果为 NaN(typeof NaN 是 ‘number’)
  • 如果是 Infinity + Infinity,结果是 Infinity
  • 如果是 -Infinity + (-Infinity),结果是 -Infinity
  • 如果是 Infinity + (-Infinity),结果是 NaN

如果加号两边有至少一个是字符串,其规则为:

  • 如果 + 号两边都是字符串,则执行字符串拼接
  • 如果 + 号两边只有一个值是字符串,则将另外的值转换为字符串,再执行字符串拼接
  • 如果 + 号两边有一个是对象,则调用 valueof() 或者 toStrinig() 方法取得值,转换为基本类型再进行字符串拼接。

当然也可以进行显式转换,我们往往使用类似 Number、Boolean、String、parseInt 等方法,进行显式类型转换。

JavaScript 函数参数传递

我们知道 JavaScript 当中有“引用赋值”和“基本类型赋值”以及相关概念:“深拷贝”、“浅拷贝”区分。那么函数的参数传递有什么讲究呢?请看例题:

let foo = 1
const bar = value => {value = 2console.log(value)
}
bar(foo)
console.log(foo) 

两处输出分别为 2、1;也就是说在 bar 函数中,参数为基本类型时,函数体内复制了一份参数值,而不会影响参数实际值。

let foo = {bar: 1}
const func = obj => {obj.bar = 2console.log(obj.bar)
}
func(foo)
console.log(foo)

两处输出分别为 2、{bar: 2};也就是说如果函数参数是一个引用类型,当在函数体内修改这个引用类型参数的某个属性值时,将会对参数进行修改。因为这时候函数体内的引用地址指向了原来的参数。

但是如果在函数体内,直接修改了对参数的引用,则情况又不一样:

let foo = {bar: 1}
const func = obj => {obj = 2console.log(obj)
}
func(foo)
console.log(foo)

两处输出分别为 2、{bar: 1};这样的情况理解起来较为晦涩,其实总结下来就是:

  • 参数为基本类型时,函数体内复制了一份参数值,对于任何操作不会影响参数实际值
  • 函数参数是一个引用类型时,当在函数体内修改这个值的某个属性值时,将会对参数进行修改
  • 函数参数是一个引用类型时,如果我们直接修改了这个值的引用地址,则相当于函数体内新创建了一份引用,对于任何操作不会影响原参数实际值

cannot read property of undefined 问题解决方案

这里我们分析一个常见的 JavaScript 细节:cannot read property of undefined 是一个常见的错误,如果意外的得到了一个空对象或者空值,这样恼人的问题在所难免。

考虑这样的一个数据结构:

const obj = {user: {posts: [{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },{ title: 'Bar', comments: [ 'Ok' ] },{ title: 'Baz', comments: []}],comments: []}
}

为了在对象中相关取值的过程,需要验证对象每一个 key 的存在性。常见的处理方案有:

  • && 短路运算符进行可访问性嗅探
obj.user &&
obj.user.posts &&
obj.user.posts[0] &&
obj.user.posts[0].comments
  • || 单元设置默认保底值
(((obj.user || {}).posts||{})[0]||{}).comments 
  • try…catch
var result
try {result = obj.user.posts[0].comments
}
catch {result = null
}

最后,TC39 提案中有一个新的提案,支持:
console.log(obj?.user?.posts[0]?.comments)

由此可见,JavaScript 语言也在不断演进。通过这个案例,想告诉大家:熟练掌握基础环节,将对于进阶起到关键作用。

分析一道网红题目

综合以上知识点,我们来看一道“网红”题目:
Can (a == 1 && a == 2 && a == 3) ever evaluate to true?
即:
a == 1 && a == 2 && a == 3 可能为 true 吗?
直观上分析,如果变量 a 是一个基本 Number 类型,这是不可能为 true 的,因此解题思路也需要从变量 a 的类型及(对象)转换(基本类型)上来考虑。

方案一:

const a = {value: 1,toString: function () {return a.value++}
}
console.log(a == 1 && a == 2 && a == 3) // true

这个方案中,我们将 a 定义为一个对象,并重写了其 toString 方法。因此在每次进行判断时,按照规则,== 号两边出现了对象类型,另一边是 Number 类型,需要调用 a 对象 toString 方法,toString 方法的返回值会作为对象转为基本类型的值,我们每次将 value 属性加 1。同样,如果按照相同的方式重写 valueOf 方法,也是可以达到同样目的的。

方案二:

let value = 0
Object.defineProperty(window, 'a', {get: function() {return ++value}
})console.log(a == 1 && a == 2 && a == 3) // true

这里我们将 a 作为属性,挂载在 window 对象当中,重写其 getter 方法。

JavaScript 类型判断总结

  • 通过 x === null 来判断 null 类型
  • 对于 typeof x 不为 object 的情况,直接返回 typeof x 结果,这时候可以判断出 number,string,boolean,undefined,symbol 类型
  • Object.prototype.toString 方法,该方法确实可以称得上“终极方案”。对返回结果使用 .slice(8, -1),更加方便拿到结果:
Object.prototype.toString.call(true).slice(8, -1)
// "Boolean"

相关文章:

JavaScript 类型判断及类型转换规则

文章目录 JavaScript 类型及其判断使用 typeof 判断类型使用 instanceof 判断类型使用 constructor 和 Object.prototype.toString 判断类型JavaScript 类型及其转换JavaScript 函数参数传递cannot read property of undefined 问题解决方案分析一道网红题目JavaScript 类型判断…...

ubuntu禁用/启用图形界面

当安装了带图形界的ubuntu的时候,如果觉得图形界面占资源,就需要将图形界面关闭,关闭的方法如下: 1、 打开 /etc/default/grub,修改或增加如下参数: GRUB_CMDLINE_LINUX_DEFAULT"text" GRUB_TE…...

【LeetCode】28. 找出字符串中第一个匹配项的下标(简单)——代码随想录算法训练营Day09

题目链接:28. 找出字符串中第一个匹配项的下标 题目描述 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分&#xff…...

架设一台NFS服务器

1、开放/nfs/shared目录,供所有用户查询资料 2、开放/nfs/upload目录,为192.168.xxx.0/24网段主机可以上传目录, 并将所有用户及所属的组映射为nfs-upload,其UID和GID均为210 3、将/home/tom目录仅共享给192.168.xxx.xxx这台主机&#xf…...

MySQL中根据出生日期计算年龄

创建student表 mysql> create table student( -> sid int primary key comment 学生号, -> sname varchar(20) comm…...

ABAP IDOC 2 XML

有个需求,外围系统希望我们给到一个IDOC 记录的样例,但是我们we02中并无法看到 就找了一个demo去直接展示IDOC内容 *&---------------------------------------------------------------------* *& Report Z_IDOC_TO_XML *&------------…...

什么是小程序?特点和技术架构详解

小程序是一种新的移动应用程序格式,一种结合了 Web 技术以及客户端技术的混合解决方案。 传统的原生应用运行起来比较流畅,但是也有天然的基因缺陷: 不支持动态化,发布周期长需要开发Android和iOS两套代码,开发成本高…...

边缘计算的挑战和机遇——数据安全与隐私保护

边缘计算的挑战和机遇 边缘计算面临着数据安全与隐私保护、网络稳定性等挑战,但同时也带来了更强的实时性和本地处理能力,为企业降低了成本和压力,提高了数据处理效率。因此,边缘计算既带来了挑战也带来了机遇,需要我…...

linux-等保三级脚本(1)

该脚本主要是针对 CentOS Linux 7 合规基线加固的一些配置操作,包括创建用户、安全审计配置、入侵防范配置、访问控制配置、身份鉴别策略配置等。如果您需要在脚本中添加公司网址,您可以在适当的位置添加相应的内容。不过请注意,在实际生产环…...

K8s面试题——情景篇

文章目录 一、考虑一家拥有分布式系统的跨国公司,拥有大量数据中心,虚拟机和许多从事各种任务的员工。您认为这样公司如何以与 Kubernetes 一致的方式管理所有任务?二、考虑一种情况,即公司希望通过维持最低成本来提高其效率和技术运营速度。…...

.NET 8.0 发布到 IIS

如何在IIS(Internet信息服务)上发布ASP.NET Core 8? 在本文中,我假设您的 Windows Server IIS 上已经有一个应用程序池。 按照步骤了解在 IIS 环境下发布 ASP.NET Core 8 应用程序的技巧。 您需要设置代码以支持 IIS 并将项目配…...

当前vscode环境下 多进程多线程运行情况探究

我的代码 其中在“打开图片时”、“进入子进程之前”、“子进程join前”、“进入子进程区域后”,“子进程join后”、“进入子线程区域后”分别打印了进程线程的编号和数量。 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file test2.…...

使用WAF防御网络上的隐蔽威胁之命令注入攻击

命令注入攻击是网络安全领域的一种严重威胁,它允许攻击者在易受攻击的应用程序上执行恶意命令。 这种攻击通常发生在应用程序将用户输入错误地处理为操作系统命令的情况下。 什么是命令注入攻击 定义:命令注入攻击发生在攻击者能够在易受攻击的应用程…...

blender 导入到 Marvelous Designer

1) 将模型的所有部分合并为一个单独的mesh 2) 先调整计量单位: 3)等比缩放,身高调整到180cm左右 4)应用当前scale 首先,选中你要修改的物体,然后按下Ctrl-A键,打开应用…...

【Redis】AOF 源码

在上篇, 我们已经从使用 / 机制 / AOF 过程中涉及的辅助功能等方面简单了解了 Redis AOF。 这篇将从源码的形式, 进行深入的了解。 1 Redis 整个 AOF 主要功能 Redis 的 AOF 功能概括起来就 2 个功能 AOF 同步: 将客户端发送的变更命令, 保存到 AOF 文件中AOF 重写: 随着 Red…...

【小笔记】算法训练基础超参数调优思路

【学而不思则罔,思维不学则怠】 本文总结一下常见的一些算法训练超参数调优思路(陆续总结更新),包括: batchsize学习率epochsdropout(待添加) Batch_size 2023.9.29 简单来说,较…...

Blender——将模型及其所有纹理与材质导入unity

前期准备 参考视频:7分钟教会你如何将Blender的模型材质导入unity_哔哩哔哩_bilibili 实验模型官网下载地址:Hoi An Ancient House Model free VR / AR / low-poly 3D model CSDN下载链接: 【免费】Blender三维模型-古代房屋模型&#xff…...

docker-compose和docker compose的区别

在docker实际使用中,经常会搭配Compose,用来定义和运行多个 Docker 容器。使用时会发现,有时候的指令是docker-compose,有时候是docker compose,下面给出解释。 docker官方文档:https://docs.docker.com/c…...

Android NDK Crash信息收集捕获和日志异常定位分析(addr2line)

Android NDK 闪退日志收集与分析 我们在开发过程中,Android JNI层Crash问题或者我们引用的第三方.so库文件报错,都是一个比较头疼的问题。相对Java层来说,由于c/c++造成的crash没有输出如同Java的Exception Strace堆栈信息,所以定位问题也是个比较艰难的事情。 Google Br…...

5、NumPy 高级索引和切片

目录 一、切片(Slicing) 二、NumPy 高级索引详解 1. 布尔型索引 2. 列表/数组索引 3. 花式索引 (Fancy Indexing) 4. 元组索引 三、结合切片与高级索引 一、切片(Slicing) 切片操作允许访问数组的子集。在 NumPy 中&#xf…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...

SpringAI实战:ChatModel智能对话全解

一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM&#xff0…...

云原生周刊:k0s 成为 CNCF 沙箱项目

开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...

一些实用的chrome扩展0x01

简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...

Java多线程实现之Runnable接口深度解析

Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...