17道题让你彻底理解JS中的类型转换


Posted in Javascript onAugust 08, 2019

前言

类型转换是将值从一种类型转换为另一种类型的过程(比如字符串转数字,对象转布尔值等)。任何类型不论是原始类型还是对象类型都可以进行类型转换,JavaScript 的原始类型有:number, string, boolean, null, undefined, Symbol。

本文将通过 17 道题目来深入的了解 JS 中的类型转换,通过阅读本文之后,你将能自信的回答出下面题目的答案,并且能够理解背后的原理。在文章的最后,我讲写出答案并解释。在看答案之前,你可以把答案写下来,最后再对照一下,便于找出理解有误的地方。

true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
"true" == true
false == "false"
null == ""
!!"false" == !!"true"
["x"] == "x"
[] + null + 1
[1,2,3] == [1,2,3]
{} + [] + {} + [1]
! + [] + [] + ![]
new Date(0) - 0
new Date(0) + 0

类似于上面的这些问题大概率也会在 JS 面试中被问到, 所以继续往下读。

隐式 vs 显式类型转换

类型转换可以分为隐式类型转换和显式类型转换。

当开发人员通过编写适当的代码(如Number(value))用于在类型之间进行转换时,就称为显式类型强制转换(或强制类型转换)。

然而 JavaScript 是弱类型语言,在某些操作下,值可以在两种类型之间自动的转换,这叫做隐式类型转换。在对不同类型的值使用运算符时通常会发生隐式类型转换。比如 1 == null, 2 / "5", null + new Date() 。当值被 if 语句包裹时也有可能发生,比如 if(value) {} 会将 value 转换为 boolean类型。

严格相等运算符(===)不会触发类型隐式转换,所以它可以用来比较值和类型是否都相等。

隐式类型转换是一把双刃剑,使用它虽然可以写更少的代码但有时候会出现难以被发现的bug。

三种类型转换

我们需要知道的第一个规则是:在 JS 中只有 3 种类型的转换

  • to string
  • to boolean
  • to number

第二,类型转换的逻辑在原始类型和对象类型上是不同的,但是他们都只会转换成上面 3 种类型之一。

我们首先分析一下原始类型转换。

String 类型转换

String() 方法可以用来显式将值转为字符串,隐式转换通常在有 + 运算符并且有一个操作数是 string 类型时被触发,如:

String(123) // 显式类型转换

123 + '' // 隐式类型转换

所有原始类型转 String 类型

String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'

Symbol 类型转 String 类型是比较严格的,它只能被显式的转换

String(Symbol('symbol')) // 'Symbol(symbol)'

'' + Symbol('symbol') // TypeError is thrown

Boolean 类型转换

Boolean() 方法可以用来显式将值转换成 boolean 型。

隐式类型转换通常在逻辑判断或者有逻辑运算符时被触发(|| && !)。

Boolean(2) // 显示类型转换
if(2) {} // 逻辑判断触发隐式类型转换
!!2  // 逻辑运算符触发隐式类型转换
2 || 'hello' // 逻辑运算符触发隐式类型转换

注意: 逻辑运算符(比如 || 和 &&)是在内部做了 boolean 类型转换,但实际上返回的是原始操作数的值,即使他们都不是 boolean 类型。

// 返回 number 类型 123,而不是 boolean 型 true
// 'hello' 和 '123' 仍然在内部会转换成 boolean 型来计算表达式
let x = 'hello' && 123 // x === 123

boolean 类型转换只会有 true 或者 false 两种结果。

Boolean('')  // false
Boolean(0)  // false 
Boolean(-0)  // false
Boolean(NaN)  // false
Boolean(null)  // false
Boolean(undefined) // false
Boolean(false) // false

任何不在上面列表中的值都会转换为 true, 包括 object, function, Array, Date 等,Symbol 类型是真值,空对象和空数组也是真值。

Boolean({})  // true
Boolean([])  // true
Boolean(Symbol()) // true
!!Symbol()  // true
Boolean(function() {}) // true

Number 类型转换

和 Boolean()、String() 方法一样, Number() 方法可以用来显式将值转换成 number 类型。
number 的隐式类型转换是比较复杂的,因为它可以在下面多种情况下被触发。

  1. 比较操作(>, <, <=, >=)
  2. 按位操作(| & ^ ~)
  3. 算数操作(- + * / %), 注意,当 + 操作存在任意的操作数是 string 类型时,不会触发 number 类型的隐式转换
  4. 一 元 + 操作
  5. 非严格相等操作(== 或者 !== ),注意,== 操作两个操作数都是 string 类型时,不会发生 number 类型的隐式转换
Number('123') // 显示类型转换
+ '123'  // 隐式类型转换
123 != "456" // 隐式类型转换
4 > "5" // 隐式类型转换
5 / null // 隐式类型转换
true | 0 // 隐式类型转换

接下来看一下原始类型显示转换 number 类型会发生什么

Number(null)   // 0
Number(undefined)  // NaN
Number(true)   // 1
Number(false)   // 0
Number(" 12 ")   // 12
Number("-12.34")  // -12.34
Number("\n")   // 0
Number(" 12s ")  // NaN
Number(123)   // 123

当将一个字符串转换为一个数字时,引擎首先删除前尾空格、\n、\t 字符,如果被修剪的字符串不成为一个有效的数字,则返回 NaN。如果字符串为空,则返回 0。

Number() 方法对于 null 和 undefined 的处理是不同的, null 会转换为 0, undefined 会转换为 NaN

不管是显式还是隐式转换都不能将 Symbol 类型转为 number 类型,当试图这样操作时,会抛出错误。

Number(Symbol('my symbol')) // TypeError is thrown
+Symbol('123')   // TypeError is thrown

这里有 2 个特殊的规则需要记住:

1、当将 == 应用于 null 或 undefined 时,不会发生数值转换。null 只等于 null 或  undefined,不等于其他任何值。

null == 0  // false, null is not converted to 0
null == null  // true
undefined == undefined // true
null == undefined // true
undefined == 0  // false

2、NaN 不等于任何值,包括它自己

NaN === NaN // false

if(value !== value) { console.log('the value is NaN') }

object 类型转换

到这里我们已经深入了解了原始类型的转换,接下来我们来看一下 object 类型的转换。

当涉及到对象的操作比如:[1] + [2,3],引擎首先会尝试将 object 类型转为原始类型,然后在将原始类型转为最终需要的类型,而且仍然只有 3 种类型的转换:number, string, boolean

最简单的情况是 boolean 类型的转换,任何非原始类型总是会转换成 true,无论对象或数组是否为空。

对象通过内部 [[ToPrimitive]] 方法转换为原始类型,该方法负责数字和字符串转换。

[[ToPrimitive]] 方法接受两个参数一个输入值和一个需要转换的类型(Numer or String)

number 和 string的转换都使用了对象的两个方法: valueOf 和 toString。这两个方法都在 Object.prototype 上被声明,因此可用于任何派生类,比如 Date, Array等。

通常上 [[ToPrimitive]] 算法如下:

  • 如果输入的值已经是原始类型,直接返回这个值。
  • 输入的值调用 toString() 方法,如果结果是原始类型,则返回。
  • 输入的值调用 valueOf() 方法,如果结果是原始类型,则返回。
  • 如果上面 3 个步骤之后,转换后的值仍然不是原始类型,则抛出 TypeError 错误。

number 类型的转换首先会调用 valueOf() 方法,如果不是原始值在调用 toString() 方法。 string 类型的转换则相反。

大多数 JS 内置对象类型的 valueOf() 返回这个对象本身,其结果经常被忽略,因为它不是一个原始类型。所以大多数情况下当 object 需要转换成 number 或 string 类型时最终都调用了 toString() 方法。

当运算符不同时,[[ToPrimitive]] 方法接受的转换类型参数也不相同。当存在 == 或者 + 运算符时一般会先触发 number 类型的转换再触发 string 类型转换。

在 JS 中你可以通过重写对象的 toString 和 valueOf 方法来修改对象到原始类型转换的逻辑。

答案解析

接下来我们按照之前的转换逻辑来解释一下每一道题,看一下是否和你的答案一样。

true + false // 1

'+' 运算符会触发 number 类型转换对于 true 和 false

12 / '6' // 2

算数运算符会把字符串 ‘6' 转为 number 类型

"number" + 15 + 3 // "number153"

'+' 运算符按从左到右的顺序的执行,所以优先执行 “number” + 15, 把 15 转为 string 类型,得到 “number15” 然后同理执行 “number15” + 3

15 + 3 + "number" // "18number"

15 + 3 先执行,运算符两边都是 number 类型 ,不用转换,然后执行 18 + “number” 最终得到 “18number”

[1] > null // true

==> '1' > 0
==> 1 > 0
==> true

比较运算符 > 执行 number 类型隐式转换。

"foo" + + "bar" // "fooNaN"

==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"

一元 + 运算符比二元 + 运算符具有更高的优先级。所以 + bar表达式先求值。一元加号执行字符串“bar” 的 number 类型转换。因为字符串不代表一个有效的数字,所以结果是NaN。在第二步中,计算表达式'foo' + NaN。

'true' == true // false

==> NaN == 1
==> false

'false' == false // false

==> NaN == 0
==> false

== 运算符执行 number 类型转换,'true' 转换为 NaN, boolean 类型 true 转换为 1

null == '' // false

null 不等于任何值除了 null 和 undefined

!!"false" == !!"true" // true

==> true == true
==> true

!! 运算符将字符串 'true' 和 'false' 转为 boolean 类型 true, 因为不是空字符串,然后两边都是 boolean 型不在执行隐式转换操作。

['x'] == 'x' // true

== 运算符对数组类型执行 number 转换,先调用对象的 valueOf() 方法,结果是数组本身,不是原始类型值,所以执行对象的 toString() 方法,得到字符串 'x'

[] + null + 1 // 'null1'

==> '' + null + 1
==> 'null' + 1
==> 'null1'

'+' 运算符执行 number 类型转换,先调用对象的 valueOf() 方法,结果是数组本身,不是原始类型值,所以执行对象的 toString() 方法,得到字符串 '', 接下来执行表达式 '' + null + 1。

0 || "0" && {} // {}

==> (0 || '0') && {}
==> (false || true) && true
==> true && true
==> true

逻辑运算符 || 和 && 将值转为 boolean 型,但是会返回原始值(不是 boolean)。

[1,2,3] == [1,2,3] // false

当运算符两边类型相同时,不会执行类型转换,两个数组的内存地址不一样,所以返回 false

{} + [] + {} + [1] // '0[object Object]1'

==> +[] + {} + [1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + '1'
==> '0[object Object]1'

所有的操作数都不是原始类型,所以会按照从左到右的顺序执行 number 类型的隐式转换, object 和 array 类型的 valueOf() 方法返回它们本身,所以直接忽略,执行 toString() 方法。 这里的技巧是,第一个 {} 不被视为 object,而是块声明语句,因此它被忽略。计算从 +[] 表达式开始,该表达式通过toString()方法转换为空字符串,然后转换为0。

! + [] + [] + ![] // 'truefalse'

==> !(+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'

一元运算符优先执行,+[] 转为 number 类型 0,![] 转为 boolean 型 false。

new Date(0) - 0 // 0

==> 0 - 0
==> 0

'-' 运算符执行 number 类型隐式转换对于 Date 型的值,Date.valueOf() 返回到毫秒的时间戳。

new Date(0) + 0

==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'

'+' 运算符触发默认转换,因此使用 toString() 方法,而不是 valueOf()。

总结

查看原文

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
推荐:极酷右键菜单
Nov 29 Javascript
用Jquery实现可编辑表格并用AJAX提交到服务器修改数据
Dec 27 Javascript
Jquery 返回json数据在IE浏览器中提示下载的问题
May 18 Javascript
node.js中的fs.fchmodSync方法使用说明
Dec 16 Javascript
javaScript的函数对象的声明详解
Feb 06 Javascript
简介JavaScript中search()方法的使用
Jun 06 Javascript
JS选取DOM元素的简单方法
Jul 08 Javascript
js 点击a标签 获取a的自定义属性方法
Nov 21 Javascript
javascript 通过键名获取键盘的keyCode方法
Dec 31 Javascript
vue实现图片加载完成前的loading组件方法
Feb 05 Javascript
解决echarts图表使用v-show控制图表显示不全的问题
Jul 19 Javascript
echarts实现获取datazoom的起始值(包括x轴和y轴)
Jul 20 Javascript
微信小程序bindtap事件与冒泡阻止详解
Aug 08 #Javascript
vue2 拖动排序 vuedraggable组件的实现
Aug 08 #Javascript
React+TypeScript+webpack4多入口配置详解
Aug 08 #Javascript
JavaScript:ES2019 的新特性(译)
Aug 08 #Javascript
thinkjs微信中控之微信鉴权登陆的实现代码
Aug 08 #Javascript
Vue指令之 v-cloak、v-text、v-html实例详解
Aug 08 #Javascript
javascript实现blob加密视频源地址的方法
Aug 08 #Javascript
You might like
PHP 判断变量类型实现代码
2009/10/23 PHP
PHP在linux上执行外部命令的方法
2017/02/06 PHP
Js组件的一些写法
2010/09/10 Javascript
js常用排序实现代码
2010/12/28 Javascript
Javascript中string转date示例代码
2013/11/01 Javascript
关于Javascript作用域链的八点总结
2013/12/06 Javascript
解决jQuery动态获取手机屏幕高和宽的问题
2014/05/07 Javascript
Angular用来控制元素的展示与否的原生指令介绍
2015/01/07 Javascript
js实现带有介绍的Select列表菜单实例
2015/08/18 Javascript
AngularJS入门教程之过滤器详解
2016/08/19 Javascript
Angular实现的简单查询天气预报功能示例
2017/12/27 Javascript
Vue2.0 事件的广播与接收(观察者模式)
2018/03/14 Javascript
如何获取TypeScript的声明文件.d.ts
2018/05/01 Javascript
webpack4简单入门实例
2018/09/06 Javascript
深入理解 Koa 框架中间件原理
2018/10/18 Javascript
JS实现集合的交集、补集、差集、去重运算示例【ES5与ES6写法】
2019/02/18 Javascript
jQuery对底部导航进行跳转并高亮显示的实例代码
2019/04/23 jQuery
vue中datepicker的使用教程实例代码详解
2019/07/08 Javascript
VUE Elemen-ui之穿梭框使用方法详解
2021/01/19 Javascript
python解析json实例方法
2013/11/19 Python
python实现从web抓取文档的方法
2014/09/26 Python
使用Python设置tmpfs来加速项目的教程
2015/04/17 Python
Python批量合并有合并单元格的Excel文件详解
2018/04/05 Python
Python实现Dijkstra算法
2018/10/17 Python
python使用requests模块实现爬取电影天堂最新电影信息
2019/04/03 Python
三步实现Django Paginator分页的方法
2019/06/11 Python
ML神器:sklearn的快速使用及入门
2019/07/11 Python
利用Python的turtle库绘制玫瑰教程
2019/11/23 Python
Python 从attribute到property详解
2020/03/05 Python
使用numpngw和matplotlib生成png动画的示例代码
2021/01/24 Python
amazeui模态框弹出后立马消失并刷新页面
2020/08/19 HTML / CSS
香港中原电器网上商店:Chung Yuen
2019/06/26 全球购物
参赛口号
2014/06/16 职场文书
2015年政风行风工作总结
2015/04/21 职场文书
使用php的mail()函数实现发送邮件功能
2021/06/03 PHP
Kubernetes控制节点的部署
2022/04/01 Servers