类型转换

200 阅读12分钟

将值从一种类型转换为另一种类型通常称为类型转换,这是显式的情况;隐式的情况称为强制类型转换

也可以这样来区分,类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时。

你不知道的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后
true1
false0
undefinedNaN
null0
字符串遵循数字常量的相关规则/语法,失败返回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 的结果。