将值从一种类型转换为另一种类型通常称为类型转换,这是显式的情况;隐式的情况称为强制类型转换。
也可以这样来区分,类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时。
你不知道的javascript 作者认为,强制类型转换又分为 显式强制类型转换 和 隐式强制类型转换。
抽象值操作
再介绍显式和隐式强制类型转换之前,我们需要掌握字符串、数字和布尔值之前类型转换的基本规则。ToString,ToNumber,ToBoolean,ToPrimitive(这里是ToString 不是 toString, 下面的这些也是)
ToString
| 类型值 | ToString之后 |
|---|---|
| null | "null" |
| undefined | "undefined" |
| boolean | "true" / "false" |
| number | "123" / "1.07e21" |
| 普通对象 | 除非自行定义,否则 toString()(Object.prototype.toString())返回内部属性 [[Class]] 的值 // "[object Object]" |
JSON字符串化(JSON.stringify)
将 JSON 对象序列化为字符串时也用到了 ToString。
所有安全的 JSON 值 都可以使用 JSON.stringify() 字符串化
不安全的 JSON 值。
- undefined
- function
- symbol
- 循环引用
undefined,function,symbol 会返回 null
可以给对象定义 toJSON 方法
ToNumber
| 原数值 | ToNumber后 |
|---|---|
| true | 1 |
| false | 0 |
| undefined | NaN |
| null | 0 |
| 字符串 | 遵循数字常量的相关规则/语法,失败返回NaN |
| 对象 | 先 valueOf(),后 toString(),最后 toNumber() |
注意:Object.create(null) 没有 valueOf, toString 方法
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive(参见 ES5 规范 9.1 节)会首先 (通过内部操作 DefaultValue,参见 ES5 规范 8.12.8 节)检查该值是否有 valueOf() 方法。 如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
ToBoolean
假值
- undefined
- null
- false
- +0,-0,NaN
- ""
上面这些是假值,ToBoolean 为 false;
但是,new Boolean(false),new Number(0),new String("") 这些出来的是对象
var a = new Boolean( false );
var b = new Number( 0 );
var c = new String( "" );
var d = Boolean( a && b && c );
d; // true
一般来说对象都是真值
除了假值对象,例如 document.all
document.all 用来判断是否是老版本,历史遗留问题,document.all 就是假值
真值
非假值的就是真值。
显式强制类型转换
字符串和数字之间的显示转换
String() 和 Number()
var a = 42;
var b = String( a );
var c = "3.14";
var d = Number( c );
b; // "42"
d; // 3.14
还有其他的显示方法,例如 toString 和 +(加运算符)
var a = 42;
var b = a.toString();
var c = "3.14";
var d = +c;
b; // "42"
d; // 3.14
toString 里面有点隐式转换,因为基本类型没有方法,所以 javascript 引擎自动为 基本类型 创建了封装对象,然后对该对象调用 toString()。
加运算符 显示地将 c 转换为数字。
显示解析数字字符串
解析字符串中的数字 和 将字符串类型转换为数字 的返回结果都是数字。但是两者之间还是有明显的差别。
var a = "42"
var b = "42px"
Number( a ) // 42
parseInt( a ) // 42
Number( b ) // NaN
parseInt( b ) // 42
解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回 NaN。
parseInt 的坑,这里不写,有兴趣的,可以去看看 你不知道的JavaScript中卷
显示转换为布尔值
Boolean 和 !
隐式强制类型转换
隐式强制类型转换指的是那些隐蔽的强制类型转换,换句话说,你自己觉得不够明显的强制类型转换都可以算作隐式强制类型转换。
字符串和数字之间的隐式强制类型转换
var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
// 还有一种情况
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"
所以字符串,是做拼接还是做数字加法,
javascript 根据 ES5 规范, 如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。
如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。 这个地方跟 ToNumber 抽象操作处理对象的方式一样。因为数组的 valueOf() 操作无法得到简单基本类型值,于是它转而调用 toString()。
因此上面的数组相加是 "1,23,4"
这里还有一个问题,
[] + {} // [object Object]
{} + [] // 0
表面看起来是 + 号的问题,
第一行代码,[] 转换成 "",{} 转换成 "[object Object]",所以第一行答案是 "[object Object]";
第二行代码,{} 被当作一个独立的空代码块(不执行任何操作)。代码块结尾不需要分号,所以这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换为 0。
布尔值到数字的隐式强制类型转换
隐式强制类型转换为布尔值
- if (...) 语句中的条件判断表达式
- for (...;...;...) 语句中的条件判断表达式(第二个)
- while (...) 和 do..while(...)
- ? : 中的条件判断表达式
- 逻辑运算符 || (逻辑或)和 && (逻辑与)左边的操作数(作为条件判断表达式)
以上情况中,非布尔值会被隐式强制类型转换为布尔值,遵循前面介绍的 ToBoolean 抽象操作规则
|| 和 &&
选择器运算符 或者 操作数选择器运算符
在 javascript 中他们返回的并不是布尔值。 他们的返回值是两个操作数中的一个(且仅一个)。即选择两个操作数中的一个,然后返回它的值。
|| 和 && 首先会对第一个操作数执行条件判断,如果不是布尔值就进行 ToBoolean 强制类型转换,然后再执行条件判断。
|| 和 && 返回他们其中一个操作数的值,而非条件判断的结果。
宽松相等和严格相等
== 和 ===
区别:
有个错误说法,"== 检查值是否相等, === 检查值和类型是否相等",听起来蛮有道理,但是其实是错的。
正确的解释,“== 允许再相等比较中进行强制类型转换,而 === 不允许”
其实,== 和 === 都会检查操作数的类型。区别在于操作数类型不同时,他们的处理方式不同。
抽象相等
ES5 规范 11.9.3 节的 “抽象相等比较算法” 定义了 == 运算符的行为。
其中第一段规定如果两个值的类型相同,就仅比较它们是否相等。
注意情况
- NaN 不等于 NaN
- +0 等于 -0
最后定义了对象(包括函数和数组)的宽松相等 ==。两个对象指向同一个值是即视为相等,不发生强制类型转换。=== 的定义一样。
宽松不相等 != 就是 == 的相反值,!== 同理。
== 在变焦两个不同类型的值时会发生隐式强制类型转换,会将其中之一或两者都转换为相同的类型后再进行比较。
1.字符串和数字之间的相等比较
var a = 42;
var b = "42";
a === b; // false
a == b; // true
ES5 规范 11.9.3.4-5这样定义
(1)如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。
(2)如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。
2.其他类型和布尔类型之间的相等比较
== 最容易出错的一个地方是 true 和 false 于其他类型之间的相等比较。
var a = "42";
var b = true;
a == b // false
(1)如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;
(2)如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果;
所以变成了 "42" == 1。
所以避免使用 == true 和 == false
3.null 和 undefined 之间的相等比较
null 和 undefined 之间 == 也涉及隐式强制类型转换。
(1)如果 x 为 null,y 为 undefined,则结果为 true。
(2)如果 x 为 undefined,y 为 null,则结果为 true。
在 == 中 null 和 undefined 相等(它们也与其自身相等),除此之外其他值都不存在这种情况。
4.对象和非对象之间的相等比较
(1) 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
(2) 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。
头疼的案例
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕! "0" == 0 0 == 0
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕! 0 == 0
false == ""; // true -- 晕!0 == "" 0 == 0
false == []; // true -- 晕!0 == "" 0 == 0
false == {}; // false 0 == "[object Object]" 0 == NaN
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕! 0 == 0
"" == []; // true -- 晕!0 == 0
"" == {}; // false 0 == "[object Object]" 0 == NaN
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!0 == 0
0 == {}; // false 0 == "[object Object]" 0 == NaN
[] == ![] // true --晕![] == false 0 == 0
2 == [2] // true --晕! 2 == 2
"" == [null]; // true --晕! "" == ""
"true" == true // false "true" == 1 NaN == 1
toPrimitive抽象操作还是挺重要的,后面再写一下这个
抽象关系比较
a < b,
ES5 规范 11.8.5 节定义了 “抽象关系比较”,分为两个部分:比较双方都是字符串(后半部分)和其他情况(前半部分)。
比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双发强制类型转换为数字来进行比较。
var a = [42];
var b = ["43"];
a < b; // true
b < a; // false
如果比较双方都是字符串,则按字母顺序来进行比较:
var a = ["42"];
var b = ["043"];
a < b // false
奇怪的例子
var a = { b: 42 };
var b = { b: 43 };
a < b; // false [object Object] < [object Object]
a == b; // false 同类型,比的是值,所以是false
a > b; // false [object Object] > [object Object]
a <= b; // true [object Object] <= [object Object]
a >= b; // true [object Object] >= [object Object]
小结:(这一章知识点太多)
1 抽象值操作
1.1 ToString
- null 转换成 "null"
- undefined 转换成 "undefined"
- true 转换成 "true"
- 数字 遵循通用规则
- 普通对象,toString()(Object.prototype.toString())返回内部属性[[Class]]的值,如"[object Object]"
JSON.stringify()(也是用到了ToString)
安全的 JSON 值都可以使用 JSON.stringify() 字符串化
不安全的 JSON 值,返回 null
- undefined
- function
- symbol
- 循环引用的对象
1.2 ToNumber
- true 转换成 1
- false 转换成 0
- undefined 转换成 NaN
- null 转换成 0
- 字符串 遵循数字常量的相关规则/语法。处理失败时返回 NaN。
- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换成数字
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先检查该值是否有 valueOf() 方法。如果有并且返回基本类型值,就是用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
1.3 ToBoolean
(1)可以被强制类型转换为 false 的值
(2)其他(被强制类型转换为 true 的值)
以下这是假值:
- undefined
- null
- false
- +0、-0 和 NaN
- ""
除假值之外的都是真值
new Boolean(false) 也是真值,这是封装了假值的对象
2 显式强制类型转换
2.1 字符串和数字之间的显式转换
String(...) 遵循 ToString 规则
Number(...) 遵循 ToNumber 规则
a.toString() 会给基本类型值创建一个封装对象,然后给该对象调用 toString()
加号运算符(+)
2.2 显式解析数字字符串
解析 和 转换 不同,
解析允许字符串含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止,而转换不允许出现非数字字符,否则会失败并返回NaN。
parseInt, parseFloat
2.3 显式转换为布尔值
Boolean() 遵循 ToBoolean 规则
感叹号运算符(!)
3 隐式强制类型转换
3.1 字符串和数字之间的隐式强制类型转换
加号运算符(+)既能用于数字加法,也能用于字符串拼接。
var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
区分:
根据 ES5 规范 11.6.1 节,如果某个操作数是字符串或者能通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。
跟 ToNumber 抽象操作处理的方式一样。
简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法。
3.2 布尔值到数字的隐式强制类型转换
这种情况并不多见,属于特殊情况特殊处理
3.3 隐式强制类型转换为布尔值
- if()
- for()
- while() 和 do.while()
- ?:
- || 和 &&
3.4 || 和 &&
不应该叫"逻辑运算符",应该叫"选择器运算符"或者“操作数选择器运算符”
因为它们返回的不是布尔值,而是两个操作数的一个值
4 宽松相等和严格相等
== 和 ===
常见误区是“== 检查值是否相等,=== 检查值和类型是否相等”
其实正确的解释是“== 允许在相等比较中进行强制类型转换,而 === 不允许”
== 和 === 都会检查操作数的类型。区别在于操作数类型不同时它们的处理方式不同。
4.1 抽象相等
ES5 规范 11.9.3 节的“抽象相等比较算法” 定义了 == 运算符的行为。该算法简单而又全面,涵盖了所有可能出现的类型组合,以及它们进行强制类型转换的方式。
- 如果两个值的类型形同,就仅比较它们是否相等。(非常规除外,NaN 不等于 NaN, +0 等于 -0)
- 对象(包括函数和数组)的宽松相等 ==。两个对象指向同一个值时即视为相等,不发生强制类型转换。
- == 在比较两个不同类型的值时会发生隐式强制类型转换,会将其中之一或者两者都转换为相同的类型后再进行比较
- 如果Type(x)是数字,Type(y)是字符串,则返回 x == ToNumber(y)的结果;如果Type(x)是字符串,Type(y)是数字,则返回 ToNumber(x) == y的结果
- 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。
- 如果 x 为 null,y 为 undefined,则结果为 true。如果 x 为 undefined,y 为 null,则结果为 true。
- 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果; 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。