你须要知道的JavaScript隐式类型转换

前言

JavaScript做为一门弱类型语言,其变量能够任意赋值从而转换类型,这既是优势也有缺点。若是开发者明白本身的赋值或操做会引起类型转换,那么也就无所谓了,但不少状况下开发者是不清楚本身的操做可能引起隐式类型转换,这个就有点危险,并且还很差问题定位时麻烦多多。 好比下面这张来源于Here的图,相信不少人都会感谢Brendan Eich(布兰登·艾克),哈哈。javascript

笑过以后咱们开始揭开JavaScript隐式转换的一角,先了解一些预备知识,而后对这些题目解疑,最后进行一些拓展html

预备知识

==弱相等运算符

规范文档Abstract Equality Comparison的翻译过来就是下面的:java

  1. 若是x非正常值(好比x自己会抛出错误),则中断执行
  2. 若是y非正常值(同上),则中断执行
  3. 若是x的数据类型和y的数据类型相同,则返回以严格运算符执行判断的结果,即x===y的结果
  4. 若是xnullyundefined,返回true
  5. 若是xundefinedynull,返回true
  6. 若是x的数据类型是Numbery的数据类型是String,则将y转成Number,而后返回x==toNumber(y)的结果
  7. 若是x的数据类型是Stringy的数据类型是Number,则将x转成Number,而后返回toNumber(x)==y的结果
  8. 若是x的数据类型是Boolean,则将x转成Number,而后返回toNumber(x)==y的结果
  9. 若是y的数据类型是Boolean,则将y转成Number,而后返回x==toNumber(y)的结果
  10. 若是x的数据类型是StringNumber或者Symboly的数据类型是Object,则将y转成原始类型,而后返回x==toPrimitive(y)的结果
  11. 若是x的数据类型是Objecty的数据类型是StringNumber或者Symbol,则将x转成原始类型,而后返回toPrimitive(x)==y的结果
  12. 返回false

ToNumber

规范文档ToNumber翻译过来就是:数组

参数类型 结果
完成标志( 例如returnbreakthrow等) 若是参数是一个异常中断,就返回这个参数,不然就返回该参数转换成Number以后的数值
Undefined 返回NaN
Null 返回+0
Boolean 若是参数是true,返回1;若是参数是false,返回+0
Number 返回参数(不作转换)
String StringToNumber
Symbol 抛出一个TypeError异常
Object 采用下述的步骤:
1.利用ToPrimitive(argument,hint Number)的方式转成原始类型
2.将上述步骤的原始类型转成数值,即ToNumber(primValue),并返回该数值

StringToNumber

规范文档ToNumber Applied to the String Type里面东西有点多,感兴趣可自行跳转查阅,这里简单的说一下:浏览器

  1. 若是字符串中只包含数字(包括前面带加号或负号的状况),则将其转换为十进制数值,即"1"会变成1"123"会变成123,而"011"会变成11(注意:前导的零被忽略了);
  2. 若是字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(一样,也会忽略前导零);
  3. 若是字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整数值;
  4. 若是字符串是空的(不包含任何字符),则将其转换为0
  5. 若是字符串中包含除上述格式以外的字符,则将其转换为NaN

ToPrimitive

规范文档ToPrimitive。东西比较多,简单概述就是。ToPrimitive(input[, preferredType])函数接受两个参数,第一个input为被转换的数据,第二个preferredType为但愿转换成的类型(默认为default,接受的值为NumberString)。若是input不是Oject,即基础类型(Null, Undefinded, String, Boolean, Number,Symbol, 以及未来归入规范的BigInt)时,直接返回输入input。若是inputObject时,在第二个参数为空的状况下,而且inputDate的实例时,此时preferredType会被设置为String,其余为空的状况preferredType默认为Number安全

若是preferredTypeNumberToPrimitive执行过程以下:app

  1. 若是obj为原始值,直接返回;
  2. 不然调用obj.valueOf(),若是执行结果是原始值,返回之;
  3. 不然调用obj.toString(),若是执行结果是原始值,返回之;
  4. 不然抛异常。

若是preferredType为String,将上面的第2步和第3步调换,即:函数

  1. 若是obj为原始值,直接返回;
  2. 不然调用obj.toString(),若是执行结果是原始值,返回之;
  3. 不然调用obj.valueOf(),若是执行结果是原始值,返回之;
  4. 不然抛异常。

ToBoolean

规范文档ToBoolean,概述一下就是:ui

  1. undefinednull直接返回false
  2. SymbolObject永远返回true
  3. 自己就是Boolean型直接返回本来值
  4. Number型的除+0-0NaN返回false之外,其他都返回true
  5. String型中空字符串(即长度为0)返回false,其余状况返回true

解疑

1. typeof NaN //"number"

NaN,即Not a Number,不是一个数字,它自己是Number类型的,因此利用typeof判断类型是天然返回"number"。可是NaN又是一个特殊的Number类型,它永远为假,同时自身也不等于自身。因此有时候在判断一个变量是否是NaN时,能够经过是否等于自身来判断。spa

function isNaN(value) {
    return !value == value;
}

isNaN(NaN); //true
复制代码

2. 9999999999999999  //10000000000000000

JavaScript针对数值,只有Number类型,采用64bits双浮点精度,以下图所示。

  • 1位:符号位,0表示正数,1表示负数
  • 2位到第12位(共11位):指数部分,0~2047
  • 13位到第64位(共52位):小数部分(即有效数字)

由于有效位数只有53位,因此JavaScript能精确表示的最大整数就是Math.pow(2, 53),十进制就是9007199254740992,在JavaScript也设置了Number.MAX_SAFE_INTEGER(最大安全整数)和Number.MIN_SAFE_INTEGER(最小安全整数)。当JavaScript在存储超过9007199254740992的数值时,可能会存在精度丢失的状况(超过52位的会被自动去掉)。例如:

如下为摘录阮一峰的文章:

  • 符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
  • 指数部分一共有11个二进制位,所以大小范围就是02047IEEE 754规定,若是指数部分的值在02047之间(不含两个端点),那么有效数字的第一位默认老是1,不保存在64位浮点数之中。也就是说,有效数字这时老是1.xx...xx的形式,其中xx..xx的部分保存在64位浮点数之中,最长可能为52位。所以,JavaScript 提供的有效数字最长为53个二进制位。***(-1)^符号位 * 1.xx...xx * 2^指数部分***
  • 上面公式是正常状况下(指数部分在02047之间),一个数在 JavaScript 内部实际的表示形式。
  • 精度最多只能到53个二进制位,这意味着,绝对值小于等于253次方的整数,即-253253,均可以精确表示。

3. 0.1+0.2 == 0.3 //false

一样的这也是由于JavaScript数值精度丢失的缘由致使等于判断为falseJavaScript自己也考虑到了这一点,因此设置一个可接受的偏差范围,即Number.EPSILON,当两个值的差值小于等于这个可接受偏差范围时,就能够认为这两个数值时相等的。

function isEqual(num1, num2) {
    return Math.abs(num1-num2) <= Number.EPSILON;
}

isEqual(0.1 + 0.2, 0.3); //true
复制代码

4. Math.min()和Math.max()

查看规范文档可知,当Math.min()方法无参数时返回Infinity,而Math.max()无参数时返回-Infinity

JavaScript可以表示的最大数值和最小数值分别为Number.MAX_VALUENumber.MIN_VALUE中,在大多数浏览器中,它们分别是1.7976931348623157e+3085e-324。能够看到,它们都是正数,是绝对值中的最大和最小数值。

还有比他们更大或者更小的值,当计算结果获得一个超过数值范围的值,那么就会转成Ifinity(正无穷)和-Infinity(负无穷)

JavaScript也提供了2个属性保存这两个无穷值,分别是Number.NEGATIVE_INFINITY(负无穷)和Number.POSITIVE_INFINITY(正无穷)

5. []+[][]+{}

下面的解释引自《JavaScript高级程序设计第三版》:

在使用一元操做符(如+、-、++、--)时,JavaScript存在隐式类型转换。

  • 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值
  • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为NaN
  • 在应用于布尔值falsetrue是,分别转换为01
  • 在应用于对象时,先调用对象的valueOf()方法,取的一个可供操做的值。若是结果为NaN,就在调用toString()方式转成字符串,在执行前面的操做。

上述表述就是ToPrimitive的另外一种翻译。未被重定义的状况下valueOf() 方法返回指定对象的基础类型值;toString() 方法返回一个表示该对象的字符串。

在这里[]{}都是引用类型,是对象,因此先调用valueOf方法,后面调用toString方法。

第一个[]+[]Array对象重写了ObjectvalueOf方法和toString方法,valueOf返回数组自己,toString返回与没有参数(默认为逗号拼接)的 join() 方法返回的字符串相同。因此这里先返回了数组自己,没法进行+操做,在调用toString方法,变成了空字符串,两个空字符串相加,因此最后输出空字符串。

第二个[]+{}[]最终转成空字符串,+运算实际上变成了字符串拼接方法,因而{}调用Object的原生toString方法,转成了“[object Object]”,最终拼接为了“[object Object]”

6. {} + []

这个和[]+[][]+{}点类似,若是按照第五项中的解释去理解,是得不到结果的。为何呢?这要说到JavaScript引擎自己解释代码的问题了,在JavaScript解释{}时,有两种状况,一种是语句块,一种是对象定义。

当直接在控制台输入{}+[]时,此时解释器将{}解释为语句块,即{};+[],因此输出就变成了+[]的结果,这里+符号会强制转换,执行toNumber()操做,空数组返回数字0

若是在外面加上括号,即({}+[]),那么{}就会被解释为对象,最后返回“[object Object]”

7. true+true+true === 3

有运算操做符时,Boolean类型false转为0true转为1,因此左侧结果为3,值相等,类型也相等,故返回true

8. true - true

和上面一样的理由,转变成数值运算1-1,因此返回0

9. true == 1

相等操做符,两侧会进行toNumber操做,进行值判断,不进行类型判断。因此1 == 1,返回true

10. true === 1

全等操做符,既判断值,也判断类型,即不作类型转换,这里值虽然相同,一个为Boolean类型,一个为Number类型,因此返回false

11. (! + [] + [] + ![]).length

运算符具备优先级

这里能够看到逻辑非!操做符优先级比+高,因此第一个逻辑非!先执行,至关于!(+[])+[]进行toNumber操做返回0,而后逻辑非进行toBoolean操做返回true,而后![]执行,因而[]先进行toBoolean的操做,返回true,而后逻辑非操做变成false,而后执行从左至右+运算,即true+[]+false,变成了字符串拼接,因而返回“truefalse”,这个字符串的长度也就是9

12. 9 + "1"

不管是9+"1",仍是"1"+9,结果都是91+运算符能够是数字相加运算,也能够是字符拼接运算。可是规范文档规定了,若是+运算符两侧存在字符串时,就调用toString()方法,进行字符串拼接操做,因此这里结果都是91

13. 9 - "1"

-运算符和+运算符不一样,由于-运算符就是数字运算减的操做,因此先转成Number类型,因此不管是‘9’ -1仍是9 -‘1’,结果都是8

14. [] == 0

根据==弱相等运算符中的规则,空数组最后会执行toNumber转成数字0,因此返回true

拓展

相信经过上面的解疑,应该掌握了大部分技巧,如今再来检验一波掌握的如何。

/* 猜猜下面的输出 */

// 第一题
'true' == true

// 第二题
0 == null
复制代码

先思考一波

.

开始思考

.

思考ing

.

完成思考


就认为你们都思考一波了

1. 'true' == true

嘿嘿,确定有看错而后答错的。

不先说答案,按照流程走一波:

  1. 在这个相等运算中,左侧'true'的数据类型是String,右侧true的数据类型是Boolean
  2. 首先知足==弱相等运算符9条,因此布尔值true转成数值1,返回'true'==1的值
  3. 其次'true'==1又知足第7条,因此字符串true根据上面讲的规则,转换成NaN,故返回NaN==1
  4. 而后NaN都不等于任何值,包括它自己,即NaN==NaN返回false
  5. 最后'true'==true返回false

2. 0 == null

在这个相等运算中,左侧0的数据类型是Number,右侧null的数据类型是Null(内部Type运算的结果,与typeof运算符无关),因此根据上面的规则,前面11条都不知足,直到第12步才返回false

另附上一份图,自行按照流程走便可获得答案。

参考

  1. JS中的假值
  2. JS中相等(==)运算符详解
  3. JavaScript的精度丢失和隐式类型转换
相关文章
相关标签/搜索