强制类型转换

# 强制类型转换

知其然并且知其所以然

# 4.1 值类型转换

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

类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)

# 4.2 抽象值操作

抽象操作 ToString,它负责处理非字符串到字符串的强制类型转换。

var a = [1,2,3];
a.toString(); // "1,2,3
1
2

JSON 字符串化

工具函数 JSON.stringify(..) 在将 JSON 对象序列化为字符串时也用到了 ToString。

所有安全的 JSON 值(JSON-safe)都可以使用 JSON.stringify(..) 字符串化。安全的JSON 值是指能够呈现为有效 JSON 格式的值。

不安全的 JSON 值。undefined、function、symbol(ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合 JSON结构标准,支持 JSON 的语言无法处理它们

JSON.stringify(..) 在对象中遇到 undefined、function 和 symbol 时会自动将其忽略,在数组中则会返回 null(以保证单元位置不变)。

JSON.stringify( undefined ); // undefined
JSON.stringify( function(){} ); // undefined
JSON.stringify(
 [1,undefined,function(){},4]
); // "[1,null,null,4]"
JSON.stringify(
 { a:2, b:function(){} }
); // "{"a":2}"
1
2
3
4
5
6
7
8

对包含循环引用的对象执行 JSON.stringify(..) 会出错。

如果对象中定义了 toJSON() 方法,JSON 字符串化时会首先调用该方法,然后用它的返回值来进行序列化。

如果要对含有非法 JSON 值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义 toJSON() 方法来返回一个安全的 JSON 值

// 在a中创建一个循环引用
o.e = a;

// 循环引用在这里会产生错误
// JSON.stringify( a );

// 自定义的JSON序列化
a.toJSON = function() {
 // 序列化仅包含b
 return { b: this.b };
};
JSON.stringify( a ); // "{"b":42}"
1
2
3
4
5
6
7
8
9
10
11
12

toJSON() 应该“返回一个能够被字符串化的安全的 JSON 值”,而不是“返回一个 JSON 字符串”。

JSON.string 还有一个可选参数 space,用来指定输出的缩进格式。space 为正整数时是指定每一级缩进的字符数,它还可以是字符串,此时最前面的十个字符被用于每一级的缩进

ToNumber

true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0

抽象操作 ToPrimitive会首先检查该值是否有 valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()的返回值(如果存在)来进行强制类型转换。

如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

从 ES5 开始,使用 Object.create(null) 创建的对象 [[Prototype]] 属性为 null,并且没有 valueOf() 和 toString() 方法,因此无法进行强制类型转换。

ToBoolean

以下这些是假值:

• undefined

• null

• false

• +0、-0 和 NaN

• ""

假值的布尔强制类型转换结果为 false

最常见的例子是 document.all,它是一个类数组对象,包含了页面上的所有元素,由DOM(而不是 JavaScript 引擎)提供给 JavaScript 程序使用。它以前曾是一个真正意义上的对象,布尔强制类型转换结果为 true,不过现在它是一个假值对象。

document.all 并不是一个标准用法,早就被废止了。

# 4.3 显式强制类型转换

我们在编码时应尽可能地将类型转换表达清楚,以免给别人留坑。类型转换越清晰,代码可读性越高,更容易理解。

字符串和数字之间的转换是通过 String(..) 和 Number(..) 这两个内建函数来实现的,请注意它们前面没有 new 关键字,并不创建封装对象。

String(..) 遵循前面讲过的 ToString 规则,将值转换为字符串基本类型。Number(..) 遵循前面讲过的 ToNumber 规则,将值转换为数字基本类型。

除了 String(..) 和 Number(..) 以外 a.toString(); +c;

ES5 中新加入的静态方法 Date.now():

var timestamp = Date.now();

为老版本浏览器提供 Date.now() 的 polyfill 也很简单:

if (!Date.now) {
 Date.now = function() {
 return +new Date();
 };
}
1
2
3
4
5

我们不建议对日期类型使用强制类型转换,应该使用 Date.now() 来获得当前的时间戳,使用 new Date(..).getTime() 来获得指定时间的时间戳。

JavaScript 中字符串的 indexOf(..) 方法也遵循这一惯例,该方法在字符串中搜索指定的子字符串,如果找到就返回子字符串所在的位置(从 0 开始),否则返回 -1。

indexOf(..) 不仅能够得到子字符串的位置,还可以用来检查字符串中是否包含指定的子字符串,相当于一个条件判断

如果 indexOf(..) 返回 -1,~ 将其转换为假值 0,其他情况一律转换为真值。

Math.floor( -49.6 ); // -50
~~-49.6; // -49
1
2

解析字符串中的浮点数可以使用 parseFloat(..) 函数。

ES5 之前的 parseInt(..) 有一个坑导致了很多 bug。即如果没有第二个参数来指定转换的基数(又称为 radix),parseInt(..) 会根据字符串的第一个字符来自行决定基数。

var hour = parseInt( selectedHour.value, 10 );
var minute = parseInt( selectedMiniute.value, 10 );
1
2

从 ES5 开始 parseInt(..) 默认转换为十进制数,除非另外指定。如果你的代码需要在 ES5之前的环境运行,请记得将第二个参数设置为 10。

parseInt( 0.000008 ); // 0 ("0" 来自于 "0.000008")
parseInt( 0.0000008 ); // 8 ("8" 来自于 "8e-7")
parseInt( false, 16 ); // 250 ("fa" 来自于 "false")
parseInt( parseInt, 16 ); // 15 ("f" 来自于 "function..")
parseInt( "0x10" ); // 16
parseInt( "103", 2 ); // 2
1
2
3
4
5
6
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean( a ); // true
Boolean( b ); // true
Boolean( c ); // true
Boolean( d ); // false
Boolean( e ); // false
Boolean( f ); // false
Boolean( g ); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
!!a; // true
!!b; // true
!!c; // true
!!d; // false
!!e; // false
!!f; // false
!!g; // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

显式 ToBoolean 的另外一个用处,是在 JSON 序列化过程中将值强制类型转换为 true 或false:

var a = [ 
 1,
 function(){ /*..*/ },
 2,
 function(){ /*..*/ }
];
JSON.stringify( a ); // "[1,null,2,null]"
JSON.stringify( a, function(key,val){
 if (typeof val == "function") {
 // 函数的ToBoolean强制类型转换
 return !!val;
 }
 else {
 return val;
 }
} );
// "[1,true,2,true]"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = 42;
var b = a ? true : false;
1
2

建议使用 Boolean(a) 和 !!a 来进行显式强制类型转换

然而这里涉及隐式强制类型转换,因为 a 要首先被强制类型转换为布尔值才能进行条件判断。这种情况称为“显式的隐式”,有百害而无一益,我们应彻底杜绝。

  • 字符串和数字之间的显式转换
  • 显式解析数字字符串
  • 显式转换为布尔值

# 4.4 隐式强制类型转换

隐式强制类型转换的作用是减少冗余,让代码更简洁。

隐式强制类型转换同样可以用来提高代码可读性。

字符串和数字之间的隐式强制类型转换

var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
1
2
3
4
5
6
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4
1
2
3

a 和 b 都不是字符串,但是它们都被强制转换为字符串然后进行拼接。

有一个坑常常被提到,即 [] + {} 和 {} + [],它们返回不同的结果,分别是"[object Object]" 和 0。

var a = [3];
var b = [1];
a - b; // 2
1
2
3

布尔值到数字的隐式强制类型转换

# 4.5 宽松相等和严格相等

# 4.6 抽象关系比较

# 4.7 小结

上次更新: 2022/8/22 下午3:40:53