我是靠谱客的博主 可靠墨镜,最近开发中收集的这篇文章主要介绍JavaScript类型显/隐式转换前言类型转换的由来显示转换隐式转换练习,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

JavaScript 的一个显著特点就是灵活,是一门弱语言。灵活的反面就是猝不及防的坑多,定义的变量可以改变类型,数据类型会进行隐式转换等一系列头皮发麻的操作,下面例子你知道答案是什么吗
(!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]]
// 答案是啥

类型转换的由来

MDN介绍过JavaScript 的特点:JavaScript是一种弱类型,或者说是一种动态语言。这意味着你不用提前声明变量的数据类型,在程序运行过程中,变量的数据类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据
let a = 123
// number类型
a = 'abc'
// string类型
a = () => {} // 对象类型
// a 在赋值中不断的切换类型且不报错

ECMAScript 标准定义了 7 种数据类型 object number boolean string undefined null symbol 不同形式的值对应的类型不一样,如果我们想将值从一种类型转换为另一种类型就需要类型转换

JS 的类型转换共有两种:显示类型转换 和 隐式类型转换

  • 显示转换是指在原始值和包装对象或者运算符结合后,手动显式的改变了类型
  • 隐式转换是指在代码执行过程中,通过运算符运算或语句执行等操作,js 引擎会自动隐式的改变类型

显示转换

显示类型转换是通过 JS 提供的一些函数或运算符,可以直接将类型进行转换

Number() String() Boolean()

  • String() 将值转换为字符串基本类型
  • Number()将值转换为数字基本类型
  • Boolean()将值转换为布尔基本类型
  • parseInt() 将值转换为数字基本类型

基本类型互相转换

  • 转数字类型
// 字符串
Number('') // 0
Number(' 12 ') // 12
Number('1 1') // NaN
Number('12text') // NaN
// 布尔值
Number(true) // 1
Number(false) // 0
// 数组 
Number([0])
// 0
Number([1,2,3])
// NaN
// 注意转换数组长度位 1 时直接会忽视中括号,但是长度不为 1 时转换位 NaN
// undefined
Number(undefined) // NaN
// null
Number(null) // 0
Number()
undefinedNaN
null0
true1
false0
" "0
" 11 "(字符串头尾有空格)11
‘1 1’(空格在中间/字符串中含有非数字类型字符)NaN
011(非10进制数)9
+00
NaNNaN
  • 转字符类型
// undefined
String(undefined) // 'undefined'
// null
String(null) // 'null'
// 布尔值
String(true) // 'true'
String(false) // 'false'
// 数字
String(01)
// '1'
String(011) // '9'
String(123) // '123'
String(999999999999999999999) // "1e+21"
注意:Number类定义的 toString() 方法可以接受表示转换基数的可选参数,如果不指定此参数,转换规则将是默认基于十进进制
String()
undefined“undefined”
null“null”
true“true”
false“false”
‘’ "(空字符串)" "(空字符串)
’ 11 '(字符串头尾有空格)" 11 "
‘1 1’(空格在中间/字符串中含有非数字类型字符)“1 1”
011(非10进制数)“9”
0“0”
NaN“NaN”

任何类型与字符串相加都得到的是字符串

  • 转布尔类型
// 字符串
Boolean('') // false
Boolean('123') // true
// 数字
Boolean(0) // false
Boolean(NaN) // false
Boolean(1) // true
// undefined
Boolean(undefined) // false
// null
Boolean(null) // false
// 对象
Boolean({}) // true 非null对象
注意:undefined、null、false、+0、-0、NaN、"" 只有这些toBoolean()是 false,其余都为 true

引用类型转基本类型

  • 转字符类型
// 数组
String({}) // "[object Object]"
String([1, 2]) // "1,2" String([1, 2]) === [1, 2].join()
String([1, undefined, 2]) // "1,,2"
// 函数
String(function() {}) // "function() {}"
String(class A{}) // "class A{}"
// 日期时间函数
String(new Date()) // "Mon Dec 07 2020 17:00:03 GMT+0800 (中国标准时间)"
// 正则表达式
String(/s/) // "/s/"
String(new RegExp(/s+/)) // "/s+/"
String()
{}“[object Object]”
[] (空数组)“”
[124] (只有一个纯数字元素的数组)“124”
[1, 2]“1,2”(相当于数组arr.join()方法)
[1, undefined, 2](数组的某一项是null/undefined)“1,2”
function(){}(函数对象)“function(){}”(定义函数的代码)
new Date()(日期类)"Tue Dec 14 2021 22:59:34 GMT+0800 (中国标准时间)’
/s/ new (RegExp(/s/))(正则对象)“/s/”(正则对象字面量的字符)
  • 转数字类型
// 数组
Number([]) // 0
Number([1]) // 1
Number([1, 2]) // NaN
// 日期时间函数
Number(new Date()) // 1607334260466时间戳
// 对象
Number({}) // NaN
// 正则
Number(new (RegExp(/s/))) // NaN
Number()
{}NaN
[] (空数组)0
[124] (只有一个纯数字元素的数组)124
[1, 2]NaN
[1, undefined, 2](数组的某一项是null/undefined)NaN
function(){}(函数对象)NaN
new Date()(日期类)1607334260466(时间戳)
/s/ new (RegExp(/s/))(正则对象)NaN
小结:对象转换位数字类型过程会调用对象.valueOf() 原始值

一元 + - 运算符

  • 字符串转数字
 - "3.14" // 3.14
- "22"
// 22

+"3.14"是+运算符的一元形式(即只有一个操作数)。+运算符显式地将 ”3.14“ 转换为数字,而非数字加法运算(也不是字符串拼接)


一元运算符 - 和 + 一样,会反转数字的符号位。由于 – 会被当作递减运算符来处理,所以我们不能使用 – 来撤销反转,而应该像 - -"3.14"这样,在中间加一个空格


尽量不要把一元运算符 + (还有 - )和其他运算符放在一起使用

  • 日期时间转数字
var time = new Date()
+time

一元运算符 + 的另一个常见用途是将日期(Date)对象强制类型转换为数字,返回结果为Unix时间戳

~ 位运算符

  • 转数字
  1. Javascript 按位取反位运算符 (位非)~ ,对一个表达式执行位非(求非)运算。如 ~1 = -2 ; ~2 = -3 ; ~99 = -100;
  2. 运算符查看表达式的二进制表示形式的值,并执行位非运算
~5 // -6
// 5 二进制 101,补满 32位
00000000000000000000000000000101
// 按位取反
11111111111111111111111111111010
// 由于32位开头第一个是1,所以这是一个负数,将二进制转换成负数,需要先反码
00000000000000000000000000000101
// 之后,再+1
00000000000000000000000000000110
// 转换成十进制为6,加上符号变成负数 -6
  • 转布尔值

在 JavaScript 中有些函数用 -1 来代表执行失败,用大于等于0的值来代表函数执行成功。比如,indexOf()方法在字符串中搜索指定的字符串,如果找到就返回子字符串的位置,否则返回-1

var a = "Hello World"
if(a.indexOf("lo") != -1){
// 找到匹配
}
if(a.indexOf("ol") == -1){
// 没有找到匹配
}

~和 indexOf() 一起可以将结果强制类型转换为真/假值,如果indexOf()返回-1,~将其转换为假值0,其他情况一律转换为真值

var a = "Hello World"
~a.indexOf("lo")
// -4 ==>真值
if(~a.indexOf("lo")){
// 找到匹配
}
~a.indexOf("ol")
// 0 ==>假值 
if(!~a.indexOf("ol")){
// 没有找到匹配
}
  • ~~字位截除

~~x 能将值截除为一个整数,~~只适用于数字,更重要的是它对负数的处理与Math.floor()不同, 一般也是用于取整操作性能比 Math.floor() 好

Math.floor(-49.6)
// -50
~~-49.6 //-49
  • parseFloat() parseInt()

parseInt() 解析字符串中的数字和Number()将字符串强制类型转换为数字的返回结果都是数字。但是解析和转换两者之间还是有明显的差别, 而且 parseInt() 第二个为可选参数可以转化为不同进制的数,默认是转化为十进制的数

var a = "42"
var b = "42px"
Number(a)
//42
parseInt(a) //42
Number(b)
//NaN
parseInt(b) //42
parseInt("010101",2) // 21

parseInt() 解析允许字符串中含有非数字字符,比如"42px",解析按从左到右的顺序,如果遇到非数字字符“p”就停止。而Number()转换不允许出现非数字字符,否则会失败返回NaN

解析字符串中的浮点数可以使用parseFloat()函数。从ES5开始 parseInt() 默认转换为十进制数,除非指定第二个参数作为基数

parseInt()针对的是字符串,向parseInt()传递数字和其他类型的参数是没有用的。非字符串会首先被强制类型转换为字符串,应该避免向parseInt()传递非字符串参数

parseInt(1/0,19)
//18
// 以上答案是不是很诡异

parseInt(1/0,19) 最后的结果是18,而非报错,因为parseInt(1/0,19)实际上是parseInt(“Infinity”,19)。基数19,它的有效数字字符范围是0-9和a-i(区分大小写),以19为基数时,第一个字符"I"值为18,而第二个字符"n"不是一个有效的数字字符,解析到此为止,和"42px"中"p"一样

隐式转换

隐式转换潜藏在代码中的很多地方,主要涉及: + -、 == 和 === 、if()

+ 运算符

基本类型

1 + '1' // 11
1 + true // 2
1 + false // 1
1 + undefined // NaN
'sunshine' + true // sunshinetrue

由上面结果发现当 + 运算计算 string 类型和其他数据类型相加时,其他数据类型都会转换为 string 类型;而在其它情况下都会转换为 number 类型,但是 undefiend 类型会转换为 NaN,相加结果也是 NaN

引用类型

在+运算符两侧,如果存在引用数据类型,比如对象,数组等那又会遵循怎样的一套转换规则呢

{} + true
// [Object Object] true

当使用 + 运算符时,如果存在引用数据类型,那么它将会被转换为基本类型之后再进行运算。这就涉及对象的转换规则

小结:

对于加法操作,+运算符在 JS 语法解析中存在二义性

  • 如果 +运算符两边存在 NaN,则结果为 NaN
  • 如果是 Infinity + (-Infinity),则结果为NaN
  • 如果 -Infinity + (-Infinity),则结果是 -Infinity
  • 如果 Infinity + (-Infinity),则结果是 NaN

如果 + 运算符两边有或至少一个是字符串,则其规则如下

  • 如果 +运算符两边都是字符串,则执行字符串拼接操作
  • 如果 +运算符两边只有一个是字符串,则将另外的值转换为字符串,再执行字符串拼接操
  • 如果 +运算符两边有一个是对象,则调用 valueOf 或 toString 方法取得值,将其转换为基本类型在进行字符串拼接
  • 字符串于任意类型最后得到的都是字符串

对象的隐式转换规则(ToPrimitive)

对于多数情况来说,对象隐式转换成字符串或数字,其实调用了一个叫做ToPrimitive(obj,preferredType)的内部方法来干这件事情,此抽象方法将对象值转换为相应的基本类型值

在调用这个方法转换的时候,除了date对象走转换数字流程(即preferredType值是number)优先调用 valueOf(),其他走的都是转字符流程(即preferredType值是string)优先调用toString()。用一句话解释就是:这个对象倾向转换成什么,就会优先调用哪个方法

转换数字流程:

  • 调用 obj.valueOf(),如果执行结果是原始值返回
  • 否则为对象,调用 obj.toString(),如果执行结果是原始值
  • 否则抛异常

转字符流程:

  • obj.toString(),如果执行结果是原始值
  • 否则为对象,调用 obj.valueOf(),如果执行结果是原始值
  • 否则抛异常

常用内置对象调用toString()和valueOf()的返回情况

类型toStringvalueOf
Object“[object 类型名]”对象本身
String字符串值字符串值
Number返回数值的字符串表示。还可返回以指定进制表示的字符串,默认10进制数字值
Boolean“true” / “false”Boolean 值
Array每个元素转换为字符串,用英文逗号作为分隔符进行拼接数组本身
Date日期的文本表示,格式为Wed Jun 05 2019 18:22:32 GMT+0800 (中国标准时间)返回时间戳,等同于调用getTime()
Function函数的文本表示函数本身
RegExp正则的文本表示正则本身

注意: valueOf 及 toString方法是可以被重写的

const foo = {
toString() {
return 'sunshine'
},
valueOf() {
return 1
}
}
console.log(String(foo))
console.log(1 + foo) // 2
// 第一个console 打印结果为 sunshine。这里就涉及隐式转换,在console 打印的时候,倾向于使用 foo 对象的 toString 方法,将 foo 转换为基本数据类型
// 第二个console 打印结果为 2,这时候的隐式转换则倾向于使用 foo 对象的 valueOf 方法,将 foo 转换为基本数据类型,以执行相加操作

上面代码则对foo对象的 valueOf 和toString 进行了重写

语句与逻辑运算符

相对布尔值,数字和字符串操作中的隐式强制类型转换还算比较明显。下面的情况会发生布尔值隐式强制类型转换

  • if()语句中的条件判断表达
  • for(…; …; …)语句中的条件判断表达式
  • while()和do … while()
  • ? : 中的条件判断表达式(三目表达式)
  • 逻辑运算符||和&&左边的操作数

注意:&&和||运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值

以如果条件判断左边的操作数or表达式的结果为前提,对于||来说,如果结果为true就返回第一个操作数的值,如果为false就返回第二个操作数的值。对于&&来说,如果结果为true就返回第二个操作数的值,如果为false就返回第一个操作数的值

|| 逻辑或运算符遇真则停,都是假返回逻辑或的最后的假值

function foo(a,b) {
a = a||"hello"
b = b||"world"
console.log(a + '' + b)
}
foo()
// "hello world"
// 逻辑或常用于赋默认值

&& 逻辑或运算符遇假则停,都是真返回逻辑或的最后真值

function foo() {
console.log(a)
}
var a = 42
a && foo()
// 逻辑与常用于简写条件判断,叫做“守护运算符”,即前面的表达式为后面的表达式把关

Symbol

ES6中引入了Symbol类型,它的类型转换有一个坑。ES6允许从Symbol到字符串的显式转换,然而隐式转换会产生错误

var s1 = Symbol("cool")
String(s1)
// "Symbol(cool)"
var s2 = Symbol("not cool")
s2 + '' // TypeError
+ Symbol("11") // // TypeError

Symbol不能够被转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式都是true)

== 和 ===

== 检查值是否相等,===检查值和类型是否相等(也称全等),正确的解释是:“允许在相等比较中进行强制类型转换,而=不允许”。事实上,=都会检查操作数的类型,区别在于操作数类型不同时它们的处理方式不同

抽象相等比较算法

ES5规范11.9.3节的“抽象相等比较算法”定义了==运算符的行为。该算法简单而又全面,涵盖了所有可能出现的类型组合,以及它们进行强制类型转换的方式

比较运算x==y, 其中x和 y是值,产生true或者false。这样的比较按如下方式进行:
若Type(x)与Type(y)相同, 则 a. 若Type(x)为Undefined, 返回true。 b. 若Type(x)为Null, 返回true。 c. 若Type(x)为Number, 则 i. 若x为NaN, 返回false。 ii. 若y为NaN, 返回false。 iii. 若x与y为相等数值, 返回true。 iv. 若x 为 +0 且 y为−0, 返回true。 v. 若x 为 −0 且 y为+0, 返回true。 vi. 返回false。 d. 若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。 e. 若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。 f. 当x和y为引用同一对象时返回true。否则,返回false。
若x为null且y为undefined, 返回true。
若x为undefined且y为null, 返回true。
若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。
若Type(x) 为 String 且 Type(y)为Number,返回比较ToNumber(x) == y的结果。
若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。
若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。
若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。
返回false

主要分为 x 和 y 类型相同和类型不同的情况,类型相同时没有类型转换,类型不同时

  • x, y 为 null、undefined 两者中一个,返回true
  • x、y为 Number 和 String 类型时,则转换为 Number 类型比较
  • 有 Boolean 类型时,Boolean 转化为 Number 类型比较
  • 一个 Object 类型,一个 String 或 Number 类型,将 Object 类型进行原始转换(ToPrimitive)后,按上面流程进行原始值比较

字符串VS数字

var a = 42
var b = '42'
a == b
// true

根据第4条规则返回x == ToNumber(y)的结果:a==b是宽松相等,即如果两个值的类型不同,则对其中之一或两者都进行强制类型转换。具体怎么转换?这就需要匹配前文的“抽象相等比较算法”,寻找适应的转换规则

其它类型 VS 布尔类型

==最容易出错的一个地方是true和false与其他类型之间的相等比较 (ps: 工作中最好少用双等判断条件)

var a = '42'
var b = true
a == b
// false

结果是false,这让人很容易掉坑里。如果严格按照“抽象相等比较算法”,这个结果也就是意料之中的

根据第7条规则,若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果,即返回’42’ == 1,结果为 false

很奇怪吧?所以切记:无论什么情况下都不要使用== true和== false。

"0" == false
// true
false == 0
// true
false == ""
// true
false == []
// true
"" == 0
// true
"" == []
// true
0 == []
// true

其中有4种情况涉及== false,之前我们说过应该避免,所以还剩下后面3种。

这些特殊情况会导致各种问题,使用中要多加小心。我们要对==两边的值认真推敲,以下两个原则可以让我们有效地避免出错

  • 如果两边的值中有true或者false,千万不要使用==
  • 如果两边的值中有[]、""、或者0,尽量不要使用==

隐式强制转换在部分情况下确实很危险,为了安全起见就要使用 === (全等)

null 和 undefined

在==中 null 和 undefined 相等,这也就是说在 == 中 null 和 undefined 是一回事,可以相互进行隐式强制类型转换

null == undefined

掌握“抽象相等比较算法”,读者可以自行推倒为什么 [] == ![]返回 true

< 和 <=

var a = {b:42}
var b = {b:43}
a < b
// false
a == b
// false
a > b
// false
a <= b
// true
a >= b
// true

如果a < b和a == b结果为false,为什么a <= b和a >= b的结果会是true呢?

因为根据规范a <= b被处理为b < a,然后将结果反转。因为b < a的结果为false,所以a <= b的结果为true

这可能与我们设想的大相径庭,即<=应该是“小于或者等于”,实际上,JavaScript中<=是“不大于”的意思,即a <= b被处理为 !(b < a)

另外,规范设定NaN既不大于也不小于任何其他值(六亲不认哈哈)


练习

  1. 实现一个函数,如果其中有且仅有一个参数为true,则函数返回true
function onlyOne() {
var sum = 0
for (var i=0;i<arguments.length;i++){
if(arguments[i]){
sum += arguments[i]
}
}
return sum == 1
}
var a = true
var b = false
onlyOne(b,a,b,b,b,b)
// true

无论使用隐式转换还是显式转换,我们都可以通过修改onlyTwo()或者onlyFive()来处理更加复杂的情况,只需要将最后的条件判断从改为2或5。这比加入一大堆&&和||表达式要简洁得多

  1. 写出以下代码执行结果
+true
+[]
+new Date()
[] == false
!![]
0 == 'n'
!+[]+[]+![]
// 答案解析
// +true // 1
// +[]
//0
// +new Date() // 当前时间的时间戳
// [] == false // true
== 两边都转为数字进行比较,而不是[]转为布尔值与 false 比较,即 0 == 0
// !![] // true [] => true => !true => !!true
// 0 == 'n' // true ""、"n"(或者" "等其他空格组合)等空字符串被ToNumber强制类型转换为0。
// !+[]+[]+![] // "truefalse"
// 首先第一个 + 左边不是数值,所以它是一元运算符,将后边跟着的 [] 转化为数字 0
// 同时,最后一个 [] 左边是 ! 运算符,将 [] 转化为布尔值并取反,为 false
// 转化后的结果为 !0 + [] + false
// !0 结果为 true,[] 转化为 "",所以结果变为 true + "" + false
// 因为 第一个 + 右边有字符串,所以变为"true" + false
// 最终结果为 "truefalse"
  1. 解释下面这张图的执行结果

在这里插入图片描述

最后

以上就是可靠墨镜为你收集整理的JavaScript类型显/隐式转换前言类型转换的由来显示转换隐式转换练习的全部内容,希望文章能够帮你解决JavaScript类型显/隐式转换前言类型转换的由来显示转换隐式转换练习所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部