强制类型转换
1、值类型转换
- 将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。
- 类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
2、抽象值操作
2.1、ToString
抽象操作ToString,它负责处理非字符串到字符串的强制类型转换。
基本类型值的字符串化规则为:null转换为"null", undefined转换为"undefined", true转换为"true"。数字的字符串化则遵循通用规则,不过极小和极大的数字使用指数形式。
1.07e21对普通对象来说,除非自行定义,否则toString()(Object.prototype.toString())返回内部属性[[Class]]的值,如"[object Object]"。
数组的默认toString()方法经过了重新定义,将所有单元字符串化以后再用", "连接起来。
[1,2,3].toString() // "1,2,3"-
JSON字符串化
所有安全的JSON值(JSON-safe)都可以使用JSON.stringify(..)字符串化。安全的JSON值是指能够呈现为有效JSON格式的值。
不安全的JSON值。undefined、function、symbol(ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合JSON结构标准,其他支持JSON的语言无法处理它们。JSON.stringify(..)在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)
-
如果要对含有非法JSON值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义toJSON()方法来返回一个安全的JSON值。toJSON()应该“返回一个能够被字符串化的安全的JSON值”,而不是“返回一个JSON字符串”。
var a = { val: [1,2,3], toJSON: function(){ return this.val.slice(1) } } JSON.stringify(a) // "[2,3]"
2.2 ToNumber
- true转换为1, false转换为0。undefined转换为NaN, null转换为0。
- ToNumber对字符串的处理基本遵循数字常量的相关规则/语法。处理失败时返回NaN(处理数字常量失败时会产生语法错误)。不同之处是ToNumber对以0开头的十六进制数并不按十六进制处理(而是按十进制)
- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。为了将值转换为相应的基本类型值,抽象操作ToPrimitive(参见ES5规范9.1节)会首先(通过内部操作DefaultValue,参见ES5规范8.12.8节)检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用toString()的返回值(如果存在)来进行强制类型转换。如果valueOf()和toString()均不返回基本类型值,会产生TypeError错误。
- 从ES5开始,使用Object.create(null)创建的对象[[Prototype]]属性为null,并且没有valueOf()和toString()方法,因此无法进行强制类型转换。
2.3 ToBoolean
- 假值(falsy value)• undefined• null• false• +0、-0和NaN• ""
- 从逻辑上说,假值列表以外的都应该是真值(truthy)。
- 假值对象(falsy object):浏览器在某些特定情况下,在常规JavaScript语法基础上自己创建了一些外来(exotic)值,这些就是“假值对象”。最常见的例子是document.all,它是一个类数组对象,包含了页面上的所有元素,由DOM(而不是JavaScript引擎)提供给JavaScript程序使用。它以前曾是一个真正意义上的对象,布尔强制类型转换结果为true,不过现在它是一个假值对象。document.all并不是一个标准用法,早就被废止了。
3、显式强制类型转换
3.1 字符串和数字之间的显式转换
字符串和数字之间的转换是通过String(..)和Number(..)这两个内建函数(原生构造函数)来实现的,请注意它们前面没有new关键字,并不创建封装对象。
-
一元运算符+会将操作数显式强制类型转换为数字
- 日期显式转换为数字
var timestamp = +new Date(获取时间戳 更推荐getTime()或Date.now())
- 日期显式转换为数字
一元运算符-和+一样,并且它还会反转数字的符号位。由于--会被当作递减运算符来处理,所以我们不能使用--来撤销反转,而应该像- -"3.14"这样,在中间加一个空格,才能得到正确结果3.14。
-
奇特的~运算符 它首先将值强制类型转换为32位数字,然后执行字位操作“非”(对每一个字位进行反转)。
-
~x大致等同于-(x+1)。在-(x+1)中唯一能够得到0(或者严格说是-0)的x值是-1。也就是说如果x为-1时,~和一些数字值在一起会返回假值0,其他情况则返回真值。-1是一个“哨位值”,哨位值是那些在各个类型中(这里是数字)被赋予了特殊含义的值。例如indexOf(..)哨位值-1。 - 如果indexOf(..)返回-1, ~将其转换为假值0,其他情况一律转换为真值。
-
-
字位截除
值为正数时直接截取整数部分,Math.floor()与~~一致。
值为负数时,小数位不为0,Math.floor()会为1,~~还是直接截取
Math.floor(-52.3) // -53 ~~-52.3 // -52
3.2 显式解析数字字符串
解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
解析非字符串
parseInt( 1/0, 19) // 18parseInt(1/0, 19)实际上是parseInt("Infinity", 19)。第一个字符是"I",以19为基数时值为18。第二个字符"n"不是一个有效的数字字符,解析到此为止,和"42px"中的"p"一样。
// 题目
// [1,"2","3",10,11].map(parseInt)相当于 [1,"2","3",10,11].map(function(string,radix){})
// radix进制; radix 为(0、undefined、null)时默认十进制;radix超出2~36范围均输出 NaN;
parseInt(1,0) // radix 为(0、undefined、null)时默认十进制 ===>1
parseInt('2',1) // radix超出2~36范围均输出 NaN ===> NaN
parseInt('3',2) // radix为2 string只能为0或1 或1开头 ===> NaN
parseInt(10,3) // radix为3 使用3进制 (个人三转十计算法:27 9 3 1;五转十计算法:125 25 5 1以此类推)===>3
parseInt(11,4) // radix为4 使用4进制(64 16 4 1) ===> 5
3.3 显式转换为布尔值
- 一元运算符!显式地将值强制类型转换为布尔值。但是它同时还将真值反转为假值(或者将假值反转为真值)。所以显式强制类型转换为布尔值最常用的方法是!!,因为第二个!会将结果反转回原值。建议使用Boolean(..)和!!来进行显式转换以便让代码更清晰易读。
- 显式ToBoolean的另外一个用处,是在JSON序列化过程中将值强制类型转换为true或false
4、隐式强制类型转换
4.1 字符串和数字之间的隐式强制类型转换
- 根据ES5规范11.6.1节,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用ToPrimitive抽象操作(规范9.1节),该抽象操作再调用[DefaultValue],以数字作为上下文。(简单来说就是,如果+的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法)
-
var a = 42a + ""(隐式)和前面的String(a)(显式)之间有一个细微的差别需要注意。根据ToPrimitive抽象操作规则,a + ""会对a调用valueOf()方法,然后通过ToString抽象操作将返回值转换为字符串。而String(a)则是直接调用ToString()。
-
- 字符串强制类型转换为数字
- -是数字减法运算符,因此a -0会将a强制类型转换为数字。也可以使用a * 1和a /1,因为这两个运算符也只适用于数字,只不过这样的用法不太常见。
4.2 布尔值到数字的隐式强制类型转换
- 所有传参只允许一个真值.png
- 通过sum += arguments[i]中的隐式强制类型转换,将真值(true/truthy)转换为1并进行累加
- 无论使用隐式还是显式,我们都能通过修改onlyTwo(..)或者onlyFive(..)来处理更复杂的情况,只需要将最后的条件判断从1改为2或5。这比加入一大堆&&和||表达式简洁得多。所以强制类型转换在这里还是很有用的。
4.3 隐式强制类型转换为布尔值
- (1) if (..)语句中的条件判断表达式。
- (2) for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。
- (3) while (..)和do..while(..)循环中的条件判断表达式。
- (4) ? :中的条件判断表达式。
- (5) 逻辑运算符||(逻辑或)和&&(逻辑与)左边的操作数(作为条件判断表达式)。
4.4 ||和&& (“操作数选择器”)
- &&和||运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。
4.5 符号Symbol的强制类型转换
- 允许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误
- 符号不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是true)。
5、宽松相等和严格相等
==允许在相等比较中进行强制类型转换,而===不允许
==和===都会检查操作数的类型。区别在于操作数类型不同时它们的处理方式不同。
NaN不等于NaN +0等于-0
字符串与数字比较 ==> 字符串转数字
**其他类型和布尔类型比较 **==> 布尔值转数字。注意:
true == "42" // false (判断 1 == "42")null和undefined之间 除null和undefined以外的其他值均无法得到假阳(false positive)结果。(条件判断a == null仅在a为null和undefined时才成立,除此之外其他值都不成立,包括0、false和""这样的假值。)
-
对象和非对象之间 对象执行ToPrimitive "拆封" (不包括布尔值,布尔值首先toNumber)
之前介绍过的ToPrimitive抽象操作的所有特性(如toString()、valueOf())在这里都适用。如果我们需要自定义valueOf()以便从复杂的数据结构返回一个简单值进行相等比较,这些特性会很有帮助。
var a = "abc" var b = Object(a) a == b // true b会进行拆封,得到"abc" // 但如果a是null undefinded NaN则是false,因为没有对应的封装对象,所以null和undefined不能够被封装(boxed), Object(null)和Object()均返回一个常规对象。NaN能够被封装为数字封装对象,但拆封之后NaN == NaN返回false,因为NaN不等于NaN -
比较少见的情况
-
修改原生内置(害)
Number.prototype.valueOf = function(){ return 3 } new Number(2) == 3 // true // 2 == 3不会有这种问题,因为2和3都是数字基本类型值,不会调用Number.prototype.valueOf()方法。而Number(2)涉及ToPrimitive强制类型转换,因此会调用valueOf()。 if(a == 2 && a == 3) // 这种也是会成立的,在valueOf里进行一个值++,而a==2在a==3之前执行 -
假值的相等比较
image.png 极端情况
[] == ![] // true根据ToBoolean规则,它会进行布尔值的显式强制类型转换(同时反转奇偶校验位)。所以[] == ! []变成了[] == false"" == [null] // true[null]会直接转换为""0 == "\n" // true""、"\n"(或者" "等其他空格组合)等空字符串被ToNumber强制类型转换为00 "" [] 互相相等的情况
• 如果两边的值中有true或者false,千万不要使用==。• 如果两边的值中有[]、""或者0,尽量不要使用==。
-
6、抽象关系比较
ES5规范11.8.5节定义了“抽象关系比较”(abstract relational comparison),分为两个部分:比较双方都是字符串(后半部分)和其他情况(前半部分)。
-
比较双方首先调用ToPrimitive,如果结果出现非字符串,就根据ToNumber规则将双方强制类型转换为数字来进行比较。
var a = [42] var b = ["43"] a < b // true b < a // false
-
如果比较双方都是字符串,则按字母顺序来进行比较
var a = ["42"] var b = ["043"] a < b // false 它们分别以"4"和"0"开头。因为"0"在字母顺序上小于"4",所以最后结果为false。 var c = [4,2] var d = [0,4,3] a < b // false 比较"4,2" "0,4,3"
-
根据规范a <= b被处理为b < a,然后将结果反转。
var a = { b: 42 } var b = { b: 43 } // 同为"[object Object]" 因为b < a的结果是false,所以a <= b的结果是true a < b // false a == b // false a > b // false a <= b // true a >= b // true // 相等比较有严格相等,关系比较却没有“严格关系比较”(strict relational comparison)。也就是说如果要避免a < b中发生隐式强制类型转换,我们只能确保a和b为相同的类型,除此之外别无他法。

