你不知道的类型转换

前言

相信我们在工作中经常会遇到类型转换的需求。但是类型转换也分为隐式强制类型转换”(implicit coercion)和“显式强制类型转换”(explicit coercion)。 例如:

var a = 42;
var b = a + ""; // 隐式强制类型转换 
var c = String( a ); // 显式强制类型转换
1
2
3

显式强制类型转换明确告诉我们哪里发生了类型转换,有助于提高代码可读性和可维 护性。 隐式强制类型转换则没有那么明显,是其他操作的副作用。尤其是 == 时,有时候我们就会迷糊,不知道过程中发生了什么,感觉上好像是显式强制类型转 换的反面,实际上隐式强制类型转换也有助于提高代码的可读性,今天我们就来讲一讲两种强制类型转换.

显式强制类型转换

显式强制类型转换是那些显而易见的类型转换,旨在让代码更加清晰易读

字符串和数字之间的显式转换

    var a = 42;
     var b = String( a );
     var e = a.toString();
     var c = "3.14";
     var d = Number( c );
     var f = +c;
     b; // "42"
     e; // ''42
     d; // 3.14
     f; //3.14
1
2
3
4
5
6
7
8
9
10

toString()其中涉及隐式转换。因为 toString() 对 42 这样的基本类型值不适用,所以 JavaScript 引擎会自动为 42 创建一个封 装对象,然后对该对象调用 toString().

一元运算 + 被普遍认为是显式强制类型转换

显式解析数字字符串

解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。但解析和转换 两者之间还是有明显的差别.解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停 止。而转换不允许出现非数字字符,否则会失败并返回 NaN

    var a = "42";
     var b = "42px";
    Number( a );    // 42
     parseInt( a );  // 42
     Number( b );    // NaN
     parseInt( b );  // 42
1
2
3
4
5
6

显式转换为布尔值

我们可以使用Boolean(..) 和!!,但是!!是我们会常用的。

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

隐式强制类型转换

隐式强制类型转换指的是那些隐蔽的强制类型转换,副作用也不是很明显,会让代码变得晦涩难懂

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

根据规范,如果某个操作数是字符串或者能够通过以下步骤转换为字符串 的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。 如果 + 的其中一个操作数是字符串(或者通过以上步骤可以得到字符串), 则执行字符串拼接;否则执行数字加法

    var a = 42;
     var b = a + "";
     b; // "42"
1
2
3

这里有一个小差别需要注意:a + ""(隐式)和前面的String(a)(显式)之间有一个细微的差别需要注意。根据 ToPrimitive抽象操作规则,a + ""会对a调用valueOf()方法,然后通过ToString抽象 操作将返回值转换为字符串。而 String(a) 则是直接调用 ToString()。 例如:

    var a = {
         valueOf: function() { return 42; },
         toString: function() { return 4; }
     };
     a + "";         // "42"
     String( a );    // "4"
1
2
3
4
5
6

如果执行-操作,它们首先被转换为字符串(通过toString()),然后再转换为数字。

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

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

以下情况中,非布尔值会被隐式强制类型转换为布尔值,遵循前面介绍过的 ToBoolean 抽 象操作规则 (1)if (..)语句中的条件判断表达式。 (2)for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。 (3) while (..) 和 do..while(..) 循环中的条件判断表达式。 (4)? :中的条件判断表达式。 (5) 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)。

    var a = 42;
     var b = "abc";
     var c;
     var d = null;
     if (a) {
         console.log( "yep" ); // yep
    }
    while (c) {
       console.log( "nope, never runs" );
     }
    c = d ? a : b; 
    c;  // "abc"
    if ((a && d) || c) {
      console.log( "yep" );  // yep
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

符号的强制类型转换

宽松相等(loose equals)== 和严格相等(strict equals)=== 都用来判断两个值是否“相 等”,但是它们之间有一个很重要的区别,特别是在判断条件上,即== 允许在相等比较中进行强制类型转换,而 === 不允许。

字符串和数字之间的相等比较

字符串和数字相互比较时,首先将字符中转换为数字再来比较。 (1) 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。 (2) 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果

    var a = 42;
     var b = "42";
     a == b // true
1
2
3

其他类型和布尔类型之间的相等比较

字符串或数字等其他类型与布尔类型比较时,先将布尔类型转换为数字再来和字符串比较,接下来就是进行的字符串和数字之间的相等比较,再进行一轮转换来比较。 (1) 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果; (2) 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果

    var x = true;
     var y = "42";
     x == y; // false
1
2
3

null 和 undefined 之间的相等比较

(1) 如果 x 为 null,y 为 undefined,则结果为 true。 (2) 如果 x 为 undefined,y 为 null,则结果为 true 在 == 中 null 和 undefined 相等(它们也与其自身相等),除此之外其他值都不存在这种 情况

    var a = null;
    var b;
     a == b;     // true
     a == null;  // true
     b == null;  // true
     a == false; // false
     b == false; // false
     a == "";    // false
     b == "";   // false
     a == 0;  // false
     b == 0;  // false
1
2
3
4
5
6
7
8
9
10
11

对象和非对象之间的相等比较

对象和非对象之间的比较,首先对对象进行ToPromitive转换,再进行比较。 (1) 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果; (2) 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果

   var a = 42;
   var b = [ 42 ];
   a == b; // true
1
2
3

比较少见的情况

1.隐式转换返回其他数字

    Number.prototype.valueOf = function() {
         return 3;
     };
     new Number( 2 ) == 3;   // true
1
2
3
4

Number(2) 涉及 ToPrimitive 强制类型 转换,因此会调用 valueOf()返回3. 思考题:下面代码中 a 在什么情况下会打印 1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	conso.log(1);
}
1
2
3
4
  1. 完整性检查
        "0" == null;           // false
        "0" == undefined;      // false
        "0" == false;          // true -- 晕!
        "0" == NaN;            // false
        "0" == 0;              // true
        "0" == "";             // false
        false == null;         // false
        false == undefined;    // false
        false == NaN;          // false
        false == 0;            // true -- 晕! 
        false == "";           // true -- 晕!
        false == [];           // true -- 晕!
        false == {};           // false
        "" == null;            // false
        "" == undefined;       // false
        "" == NaN;             // false
        "" == 0;               // true -- 晕! 
        "" == [];              // true -- 晕!
        "" == {};              // false
        0 == null;             // false
        0 == undefined;        // false
        0 == NaN;              // false
        0 == [];               // true -- 晕! 
        0 == {};               // false
        [] == ![]              // true -- 反人类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

我们来看看[] == ![] 的转换过程

[] == ![] // =>
[] == false // => 
[] == 0 // =>
0 == 0 // true
1
2
3
4

按照上述规范推敲两边的值,基本就不会出错。并且这两个原则可以让我们有效地避免出错 • 如果两边的值中有 true 或者 false,千万不要使用 ==。 • 如果两边的值中有 []、"" 或者 0,尽量不要使用 ==。

最后更新时间: 10/8/2019, 7:41:17 PM