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"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/635021.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter轮播图Banner

使用插件:flutter_swiper 实现轮播图 pubspec.yaml 增加 :flutter_swiper : ^lastest_version 在项目文件夹下打开命令行执行:flutter packages get 安装插件 home_page.dart中使用swiper 程序运行:先启动虚拟设备后,执行命令f…

算法训练 day24 | 77. 组合

77. 组合 题目链接:组合 视频讲解:带你学透回溯算法-组合问题 回溯其实和递归是密不可分的,解决回溯问题标准解法也是根据三部曲来进行的。 1、递归函数的返回值和参数 对于本题,我们需要用一个数组保存单个满足条件的组合,还需要另一个结果数…

【Qt5】QString的成员函数trimmed

2024年1月19日,周五下午 QString 的 trimmed 方法是用于移除字符串两端的空白字符(空格、制表符、换行符等)的方法。它返回一个新的字符串,该字符串是原始字符串去除两端空白后的结果。 下面是一个简单的示例: #incl…

开发语音产品时设计唤醒词和命令词的技巧

在实际开发语音产品过程中,要达到好的语音识别效果,除了语音算法要给力外,设计出好的唤醒词和命令词也能起到事倍功半的效果。所以下面介绍一下如何设计中文、英文和日文的唤醒词和命令词。 中文唤醒词设计 一般为4-6个字,4个字最…

setinel 阿里的限流方式

启动命令 java -jar sentinel-dashboard-1.8.1.jar --server.port8082 账号密码都是 sentinel 导入依赖 配置文件配置 spring:cloud:#限流sentinel:transport:#默认地址dashboard: localhost:8082 #配置security验证账号和密码security:user:name: adminpassword: admin #fe…

Nginx详细介绍(并从技术层面深度剖析)

nginx介绍 1.nginx 介绍2.nginx的优势3.Nginx VS Apache3.1.内核、语言、诞生时间比较3.2.功能比较3.3.Nginx 相对 apache 的优点 4.Nginx为什么有这么多的优势?4.1.IO多路复用(I/O multiplexing【多并发】)4.2.nginx的驱动模型介绍4.3.nginx…

削峰填谷与应用间解耦:分布式消息中间件在分布式环境下并发流量控制的应用

这是《百图解码支付系统设计与实现》专栏系列文章中的第(18)篇,也是流量控制系列的第(4)篇。点击上方关注,深入了解支付系统的方方面面。 本篇重点讲清楚分布式消息中间件的特点,常见消息中间件…

MiniTab的相关性统计

相关概述 相关可以度量两个变量之间关联的强度和方向。可以在以下两种相关方法之间进行选择:Pearson 积矩相关和 Spearman 秩次相关。Pearson 相关(又称为 r)是最常见的方法,它度量两个连续变量之间的线性关系。 如果变量之间的…

通讯录项目的实现以及动态顺序表(基于顺序表)

首先我们要知道什么是顺序表: 顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝,顺序表分为静态顺序表(使⽤定⻓数组存储元素)和动态顺序表(按需申请) 静态顺序表缺点: 空间给少了不够⽤,给多了造成空间浪费 拿出来我之前以及写好了的顺序表的代码:…

SQL SERVER无法连接到服务器解决过程记录

很久没用sql server了,这几天打算更新SQL SERVER数据库:SQL看这一篇就看够了(附详细代码及截图) 这篇文章,发现连接不上服务器。 找一下解决办法。 一、打开服务界面 在键盘上按“WINR”快捷键,打开运行…

rust使用protobuf

前言 c,java,go 等直接是用 ,具体就不说了,这章主要讲述rust 使用protobuf 这章主要讲述2种 1 > protoc protoc-gen-rust plugin 2> protoc prost-build 1:环境 win10 rustrover64 25-2 下载地址 https://github.com/protocolbu…

简单实用的恒温控制器

工作原理如下:ST是WTQ-288型电接点压力式温度计,当恒温箱内的温度降低到下限时,ST的指针与下限接点接触,双向可控硅通过R被强制触发导通,接通加热器RL的电源,于是恒温箱内温度上升。ST的指针转动&#xff0…

插入排序(一)——直接插入排序与希尔排序

目录 一.前言 二.排序的概念及其运用 1.1排序的概念 1.2 常用排序算法 三.常用排序算法的实现 3.1 插入排序 3.1.1 基本思想 3.1.2 直接插入排序 3.1.3 希尔排序(缩小增量排序) 四.全部代码 sort.c sort.h test.c 五.结语 一.前言 本文我们…

航空飞行器运维VR模拟互动教学更直观有趣

传统的二手车鉴定评估培训模式存在实践性不强、教学样本不足、与实际脱节等一些固有的不足。有了VR虚拟仿真技术的加持,二手车鉴定评估VR虚拟仿真实训系统逐渐进入实训领域,为院校及企业二手车检测培训提供了全新的解决方案。 高职院校汽车专业虚拟仿真实…

DC-3靶机刷题记录

靶机下载地址: 链接:https://pan.baidu.com/s/1-P5ezyt5hUbmmGMP4EI7kw?pwdrt2c 提取码:rt2c 参考: http://t.csdnimg.cn/hhPi8https://www.vulnhub.com/entry/dc-32,312/ 官网http://t.csdnimg.cn/5mVZ7DC-3 (1).pdfhttps://…

模具制造企业ERP系统有哪些?企业怎么选型适配的软件

模具的生产管理过程比较繁琐,涵盖接单报价、车间排期、班组负荷评估、库存盘点、材料采购、供应商选择、工艺流转、品质检验等诸多环节。 有些采用传统管理手段的模具制造企业存在各业务数据传递不畅、信息滞后、不能及时掌握订单和车间生产情况,难以对…

【CF比赛记录】 —— Codeforces Round 920 (Div. 3)(A、B、C、D)

🌏博客主页:PH_modest的博客主页 🚩当前专栏:CF比赛记录 💌其他专栏: 🔴每日一题 🟡 cf闯关练习 🟢 C语言跬步积累 🌈座右铭:广积粮,缓…

【ARMv8M Cortex-M33 系列 7.1 -- xPSR | CFSR | HFSR | BFAR | MMFAR 寄存器】

文章目录 问题背景Cortex-M33 Fault 寄存器介绍xPSR (程序状态寄存器)CFSR (可配置故障状态寄存器)HFSR (硬件故障状态寄存器)BFAR (总线故障地址寄存器)MMFAR (内存管理故障地址寄存器) 问题背景 由于在RA4M2(Cortex-M33)移植RT-Thread OS的时候遇到了…

第十五届蓝桥杯单片机组——串口通信UART

文章目录 一、什么是串口通信二、UART重要参数三、利用STC-ISP生成初始化代码四、使用UART发送和接收数据 一、什么是串口通信 微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。   并行通信:指数据的各位…

亚马逊云科技 WAF 部署小指南(六)追踪 Amazon WAF Request ID,排查误杀原因

众所周知,中国是全球制造业的巨大力量,许多中国企业通过 2B 电商平台网站进行商品销售和采购。在这些电商平台上,Web 应用防火墙(WAF)成为不可或缺的安全工具。然而,WAF 也可能导致误杀问题。一旦误杀发生&…