深度剖析之由浅入深揭秘JavaScript类型转换(最全总结篇)

103 阅读18分钟

前言

系列首发于公众号『前端进阶圈』 ,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。

深度剖析之由浅入深揭秘JavaScript类型转换(最全总结篇)

值类型转换

  • 将值从一种类型转换为另一种类型通常称为类型转换,分为 隐式强制类型转换显示强制类型转换。两者的区别在于是否可直观看出。
  • 如下代码:

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

    b; // 42 c; // 42

  • 上述代码中,对于 b 而言是隐式转换,而对于 c 而言是显示转换。

抽象值操作

  • 在值类型转换前我们先来看看字符串、数字、布尔值之前的基本转换规则。

    toString

  • 该操作,负责处理非字符串到字符串的强制类型转换。
  • 例如:null -> "null",undefined -> "undefined", true -> "true"
  • 对于普通对象来说,除非自定义,否则都会调用其内部的 toString() 方法。
  • Array

    • 数组的toString() 方法经过了重定义,会将所有数组元素用 , 连接起来。

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

      a.toString(); // 1,2,3

  • JSON

    • 工具函数 JSON.stringify() 在将 JSON 对象序列化为字符串时也用到了 toString
  • 对于大多数简单值来说,JSON 字符串与 toString() 的结果基本相同。但对于某一些值来说不同,例如:undefined, function, symbol, 循环引用(对象之间的相互引用,行程一个无限循环),JSON.stringify() 就无法处理它。
  • 如下代码:

    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}"
    JSON.stringify([undefined, Object, Symbol("")]);  // '[null,null,null]'

    JSON.stringify([1, undefined, function () {}, 4]); // "[1,null,null,4]" JSON.stringify({ a: 2, b: function () {} }); // "{"a":2}" JSON.stringify([undefined, Object, Symbol("")]); // '[null,null,null]'

  • 对于包含循环引用的对象执行 JSON.stringify() 会出错。但我们可以通过 toJSON() 方法进行重写。
  • 如下代码:

    // eg1:
    var o = { };
    
    
    
    
    var a = {
    b: 42,
    c: o,
    d: function(){}
    };
    // 在a中创建一个循环引用
    o.e = a;
    
    
    
    
    // 循环引用在这里会产生错误
    // JSON.stringify( a ); // TypeError: Converting circular structure to JSON
    
    
    
    
    // 自定义的JSON序列化
    a.toJSON = function() {
    // 序列化仅包含b
    return { b: this.b };
    };
    
    
    
    
    JSON.stringify( a ); // "{"b":42}"
    
    
    
    
    // eg2:
    var a = {
    val: [1,2,3],
    
    
    
    
    // 可能是我们想要的结果!
    toJSON: function(){
    return this.val.slice( 1 );
    }
    };
    JSON.stringify( a ); // "[2,3]"

    // 可能是我们想要的结果! toJSON: function(){ return this.val.slice( 1 ); } }; JSON.stringify( a ); // "[2,3]"

  • 还有关于 JSON.stringify() 的不太为人知的功能:

    • 可向 JSON.stringify() 中传递第二个参数 replacer, 可以是数组或函数
    • 如果 replacer 为数组, 那它必须为字符串数组,数组中包含了要序列化要处理的对象 key, 除此之外的属性则会被忽略
    • 如果 replacer 为函数,那它会对对象本身调用一次,然后对对象中的每个属性各调用一次,可传递两个参数,键和值。
  • 如下代码:

    var a = {
    b: 42,
    c: "42",
    d: [1,2,3]
    };
    
    
    
    // replacer 为数组
    JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"
    
    // replacer 为函数
    JSON.stringify( a, function(k,v){
    if (k !== "c") return v;
    } );
    // "{"b":42,"d":[1,2,3]}"</code></pre></li><li><p><code>还有一个可选参数 space,用于指定缩进格式。</code></p><pre><code class="js">var a = {
    b: 42,
    c: "42",
    d: [1,2,3]
    };
    
    JSON.stringify( a, null, 3 );
    // "{
    //    "b": 42,
    //    "c": "42",
    //    "d": [
    //       1,
    //       2,
    //       3
    //    ]
    // }"</code></pre></li><li><p>总结:</p><ul><li><code>对于字符串,数字,布尔值和 null,结果与 toString() 基本相同。</code></li><li><p><code>如果在 JSON.stringify() 的对象中重定义了 toJSON() 方法,那该方法会在字符序列化前调用。</code></p><h4>toNumber</h4></li></ul></li><li>其中 true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。</li><li><p>将值转换时会遵循以下规则:</p><ul><li><p><code>在使用 Number() 或 toNumber() 方法将一个字符串转换为数字时,如果字符串中出现非数字字符,则会返回 NaN。</code></p><pre><code class="js">let obj = {
    // 首先调用
    [Symbol.toPrimitive]() {
    return 200;
    },
    // 中间调用
    valueOf() {
    return 300;
    },
    // 最后调用
    toString() {
    return 'Hello';
    }
    }
    console.log(obj + 200); // 400</code></pre><h4>toBoolean</h4></li></ul></li><li><p>以下这些是假值:</p><pre><code class="js">• undefined
    • null
    • false
    • +0、-0 和 NaN
    • ""
    
    // 假值的布尔强制类型转换结果为 false。</code></pre></li><li><p>我们可以这么理解除了以上假值列表意外的值都是真值。</p><pre><code class="js">// eg1:
    var a = new Boolean(false);
    var b = new Number(0);
    var c = new String("");
    console.log(!!(a &amp;&amp; b &amp;&amp; c)); // true
    
    // eg2:
    var a = 'false';
    var b = '0';
    var c = "''";
    console.log(!!(a &amp;&amp; b &amp;&amp; c)); // true</code></pre><h4>toPromitive</h4></li><li><p>转换规则:</p><ul><li>如果检查该值是否有 valueOf 方法,看是否会返回原始值,如果返回值是原始值,则直接使用。否则,就使用 toString 方法,如果 toString 方法返回的是原始值,则直接使用,否则抛出 TypeError 错误。</li></ul><pre><code class="js">"0" == false; // true
    false == 0; // true
    false == ""; // true
    false == []; // true
    "" == 0; // true</code></pre></li><li><p>== []; // true</p></li><li><p><code>~(非) 运算符</code></p><ul><li>对于非运算符的理解:我们可理解为<code> ~ 会返回 2 的补码</code>。</li></ul></li><li><p>如下:</p><pre><code class="js">// ~x 大致等于 -(x + 1)。
    
    ~42; // -(42 + 1) ==&gt; -43</code></pre></li><li><p><code>在 -(x + 1) 中唯一能够得到 0 的 x 值是 -1。也就是如果 x 为 -1 时,~ 与其他一些数字值会返回 false 值,否则返回 true 值。</code></p><pre><code class="js">// before
    var a = "Hello World";
    
    if (a.indexOf( "lo" ) &gt;= 0) {   // true
    // 找到匹配!
    }
    if (a.indexOf( "lo" ) != -1) {  // true
    // 找到匹配!
    }
    
    if (a.indexOf( "ol" ) &lt; 0) {    // true
    // 没有找到匹配!
    }
    if (a.indexOf( "ol" ) == -1) {  // true
    // 没有找到匹配!
    }
    
    // after
    var a = "Hello World";
    
    ~a.indexOf( "lo" );         // -4   &lt;-- 真值!
    
    if (~a.indexOf( "lo" )) {   // true
    // 找到匹配!
    }
    
    ~a.indexOf( "ol" );         // 0    &lt;-- 假值!
    !~a.indexOf( "ol" );        // true
    
    if (!~a.indexOf( "ol" )) {  // true
    // 没有找到匹配!
    }</code></pre></li><li><p>显示转换为布尔值</p><pre><code class="js">// before
    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
    
    
    
    // after
    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</code></pre></li><li><p>在if() 判断中,如果没有使用 Boolean() 和 !!, 就会自动隐式进行 toBoolean 转换。</p><h3 id="item-0-5">隐式强制类型转换</h3></li><li><p><code>+</code> 运算符既能用于加法运算,也能用于字符串拼接。</p><pre><code class="js">var a = "42";
    var b = "0";
    
    var c = 42;
    var d = 0;
    
    a + b; // "420"
    c + d; // 42</code></pre></li><li><p>S: <code>如果 + 运算符中其中一个操作数是字符串,则执行字符串拼接,否则执行加法运算。</code></p><h4>隐式强制类型转换为布尔值</h4></li><li>(1) if (..) 语句中的条件判断表达式。</li><li>(2) for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。</li><li>(3) while (..) 和 do..while(..) 循环中的条件判断表达式。</li><li>(4) ? : 中的条件判断表达式。</li><li>(5) 逻辑运算符 ||(逻辑或)和 &amp;&amp;(逻辑与)左边的操作数(作为条件判断表达式)。</li><li><p><code>|| 和 &amp;&amp;</code></p><ul><li><p>他们的<code>返回值两个操作数中的其中一个</code>。</p><pre><code class="js">var a = 42;
    var b = "abc";
    var c = null;
    
    a || b;     // 42
    a &amp;&amp; b;     // "abc"
    
    c || b;     // "abc"
    c &amp;&amp; b;     // null</code></pre></li></ul></li><li><code>|| 和 &amp;&amp; 操作符会对第一个操作数进行条件判断,且会对第一个操作数进行隐式类型转换(会通过 toBoolean 操作),然后再进行条件判断。</code></li><li><code>|| 运算符,如果条件判断结果为true, 就返回第一个操作数的结果。如果为 false, 就返回第二个操作数的结果。</code></li><li><p><code>&amp;&amp; 运算符则相反,如果条件判断结果为 true 就返回第二个操作数结果,如果为 false, 就返回第一个操作数的结果。</code></p><pre><code class="js">a || b;
    // 大致相当于
    a ? a : b;
    
    a &amp;&amp; b;
    // 大致相当于
    a ? b : a;</code></pre><h3 id="item-0-6">宽松相等(==)和严格相等(===)</h3></li><li>宽松相等 == 与严格相等 === 都是用于判断两个值是否相等。但他们之间有一个重要的区别,特别是在<code>判断条件上</code>。</li><li><p>在之前的了解和很多文章中很多人这样聊到: <code>== 检查值是否相等, === 检查值和类型是否相等</code>。这么说听起来蛮有道理,但不够准确。正确的解释应该是: <code>== 允许在相等比较中进行强制类型转换,而 === 不允许</code></p><h4>两种操作符的性能</h4></li><li>根据第一种(<code>== 检查值是否相等, === 检查值和类型是否相等</code>)解释:严格相等(===) 比 宽松相等(==) 似乎做的事情更多,因为它还要检查值的类型。而第二种(<code>== 允许在相等比较中进行强制类型转换,而 === 不允许</code>) 解释: 宽松相等(==) 似乎做的事情更多,如果值类型不同还需要进行强制类型转换。</li><li>这样下来,有人觉得 == 比 === 慢,实际上确实 == 在强制类型转换的时候需要花费时间,但这个时间为微妙级的(百万分之一秒)。所以,<code>在进行比较两个值类型相同的情况下,使用 == 与 === 没有什么区别</code>。<code>如果两个值类型不同,这时候就要考虑有没有强制类型转换的必要,有就用 ==,没有就用 ===,不需要在乎性能</code>。</li><li><p>== 和 === 他们都会检查操作数的类型,区别在于操作数类型不同时他们的处理方式的不同。</p><h6>字符串和数字之间的相等比较</h6><pre><code class="js">var a = 42;
    var a = "42";
    
    a === b; // false
    a == b; // true</code></pre></li><li>a === b 因为没有强制类型转换,所以 a === b 为 false, 也就是 42 和 "42" 不相等。</li><li>a == b 因为是宽松相等,即当两个值类型不同时,则对其中一个值进行强制类型转换。那如何转换,是转换 a,还是转换 b 呢?</li><li><p>ES5 规范 11.9.3.4-5 规则:</p><ul><li><code>如果 Type(x) 为数字,Type(y) 为字符串,则返回 x == toNumber(y) 的结果</code></li><li><code>如果 Type(x) 为字符串,Type(y) 是数字,则返回 toNumber(x) == y 的结果</code></li></ul></li><li><p>所以根据以上规则,"42" 会被进行 toNumber 转换然后进行相等比较,所以 42 == 42 为 true。</p><h6>其他类型与布尔类型之间的相等比较</h6></li><li><p>== 很容易出错的一个地方就是 true、false 和其他类型之间的相等比较。</p><pre><code class="js">var a = "42";
    var b = true;
    
    a == b; // false</code></pre></li><li>我们知道变量 a 为字符串 "42" 是一个真值,那为什么 a == b 会返回 false 呢?</li><li><p>ES5 规范 11.9.3.6-7 规则:</p><ul><li><code>如果 Type(x) 是布尔类型,则返回 toNumber(x) == y 的结果</code></li><li><code>如果 Type(y) 是布尔类型,则返回 x == toNumber(y) 的结果</code></li></ul></li><li>所以根据以上规则, Type(b) 为布尔类型,所以会对 b 进行 toNumber 操作,然后就是 true = 1, 根据字符串与数字之间的比较规则可得出 42 != 1,所以结果为 false。</li><li>所以现在你搞懂了吗???</li><li>"42" 是一个真值没错,但 "42" == true 并没有发生布尔值比较和强制类型转换。这里并不是 "42" 转换为布尔值,而是 true 进行 toNumber 操作。</li><li>所以我们要搞清 == 对不同类型的组合会怎样处理,<code>== 两边的布尔值会进行 toNumber 操作</code>。</li><li><p>所以建议大家不管什么情况下都不要使用 == true 和 == false 来判断。但对于 === 来说,它不会发生强制类型转换,所以不需要进行 toNumber 操作。</p><pre><code class="js">var a = "42";
    // 不要这样用,条件判断不成立:
    if (a == true) {
    // ..
    }
    
    // 也不要这样用,条件判断不成立:
    if (a === true) {
    // ..
    }
    
    // 这样的显式用法没问题:
    if (a) {
    // ..
    }
    
    // 这样的显式用法更好:
    if (!!a) {
    // ..
    }
    
    // 这样的显式用法也很好:
    if (Boolean( a )) {
    // ..
    }</code></pre><h6>null 和 undefined 之间的相等比较</h6></li><li><p>ES5 规范 11.9.3.2-3 规则:</p><ul><li><code>如果 x 为 null, y 为 undefined, 则结果为 true</code></li><li><code>如果 x 为 undefined, y 为 null, 则结果为 true</code></li></ul></li><li><p>在 == 中 null 和 undefined 相等且他们也与自身相等,除此之外不存在这种情况。也就是说<code>在 == 中的 null 和 undefined 是一回事,可进行隐式的强制类型转换</code>。</p><pre><code class="js">var a = null;
    var b;
    
    a == b;     // true
    a == null;  // true
    b == null;  // true
    null == undefined;  // true
    null == null;  // true
    undefined == undefined;  // true
    
    a == false; // false
    b == false; // false
    a == "";    // false
    b == "";    // false
    a == 0;     // false
    b == 0;     // false</code></pre></li><li><p>所以我们<code>可将 null 和 undefined 作为等价来处理</code>。</p><pre><code class="js">var a = doSomething();
    
    if (a == null) {
    // ..
    }</code></pre></li><li><p>以上的 if 判断语句只有当 a 为 null 或 undefined 时才成立,除此之外都不成立,包含 0, false 和 ''。</p><h6>对象与非对象之间的相等比较</h6></li><li>关于对象(对象、函数、数组)与基本类型(字符串、数字,布尔值)之间的相等比较。</li><li><p>ES5规范 11.9.3.8-9 规则如下:</p><ul><li><code>如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == toPromitive(y) 的结果</code></li><li><p><code>如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 toPromitive(x) == y 的结果</code></p><pre><code class="js">var a = 42;
    var b = [ 42 ];
    
    a == b; // true</code></pre></li></ul></li><li>[ 42 ] 首先调用 toPromitive 抽象操作,返回 "42",变成 "42" == 42,然后又变成 42 == 42,最后二者相等。</li></ul><h6>比较少见的情况</h6><ul><li>如何让同时 a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3?</li><li>其中不能用同时,因为 a = 1a = 2 之前执行,a = 2a = 3 之前执行。</li><li><p>如下代码:</p><pre><code class="js">// 方法一:
    var a = {
    i: 1,
    [Symbol.toPrimitive]() {
        return this.i++;
        }
        };
        if (a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) {
        console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
        }
        
        // 方法二:
        var a = {
        i: 1,
        valueOf() {
            return this.i++;
            },
            };
            if (a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) {
            console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
            }
            // 如果让 a.valueOf() 每次调用都产生副作用,比如第一次返回 1, 第二次返回 2,以此类推,就会产生这种情况。
            
            // 方法三:
            var a = {
            i: 1,
            toString() {
                return this.i++;
                },
                };
                if (a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) {
                console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
                }</code></pre></li><li><p>在 == 隐式强制类型转换中最令人头疼的就是假值得相等比较。</p><pre><code class="js">"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</code></pre></li><li>== null;             // false</li><li>== undefined;        // false</li><li>== NaN;              // false</li><li>== [];               // true -- 晕!</li><li><p>== {};               // false</p></li><li>以上的这 24种情况 中有 17 中我们比较好理解,但有 7 中不好理解。</li><li><p>那<code>如何安全使用 == 操作符呢?</code></p><ol><li><code>如果两边的值有 true 或 false, 千万不要使用 ==</code></li><li><code>如果两边的值有 []、""、0, 千万不要使用 ==</code></li></ol></li></ul><h3 id="item-0-7">抽象关系比较</h3><ul><li>在我们日常的代码中,可能会存在 a &lt; b 这种情况的判断,但这里面也涉及了隐式强制类型转换,有必要要了解一下。</li><li>会发生隐式强制类型转换的算法只会针对于 <code>a &lt; b, a = "" &gt; b 会被处理为 b &lt;&gt;</code></li><li><p>ES5 规则:</p><ul><li><p><code>比较双方会首先调用 toPromitive,如果结果中出现非字符串,就根据 toNumber 的规则将双方强制类型转换为数字进行比较</code></p><pre><code class="js">// 如下:
                var a = 42;
                var b = "43";
                a &lt; b; // true 这里为什么会返回 true, 先保留疑惑,后面会解答
                b &lt; a; // false
                
                
                // 再比如:如果比较双方都是字符串,则按照字母顺序进行比较:
                var a = ["42"];
                var b = ["043"];
                a &lt; b; // false
                b &lt; a; // true
                
                
                
                // 再比如:如果比较双方都是字符串, 则会进行 toPromitive 操作
                var a = {b: 42};
                var b = {b: 43};
                a &lt; b; // false
                b &lt; a; // false
                // 因为 a = [object Object], b 也是 [object, Object],所以按照字母顺序排序 a &lt; b, b &lt; a 不成立。
                
                
                
                // 再比如:
                var a = {b: 42};
                var b = {b: 43};
                a &lt; b; // false
                a == b; // false
                a &gt; b; // false
                a &lt;= b; // true
                a &gt;= b; // true
                
                // 此时你可能会好奇 a &lt; ba == b 都是 false,为什么 a &lt;= ba &gt; b 为 true?
                // 因为根据规则 a &lt;= b 会被处理为 b &lt; a, 然后将结果反转。(如果没懂,回头看这段实例代码)</code></pre></li></ul></li><li>上面的结果可能与我们设想的大相径庭,相等比较有严格相等,关系比较却没有严格相等,也就是说<code>如果要避免  a &lt; b 之间的隐式强制类型转转,就只能确保 ab 为相同的类型, 或进行显示的强制类型转换。</code></li></ul><h3 id="item-0-8">小结</h3><ol><li><p>值类型转换规则:</p><ul><li><code>toString: 对于普通对象来说,除非自定义,否则都会调用其内部的 toString() 方法。</code></li><li><code>toNumber: 在使用 Number() 或 toNumber() 方法将一个字符串转换为数字时,如果字符串中出现非数字字符,则会返回 NaN。</code></li><li><code>toBoolean: 除 undefined、null、false、+0、-0 和 NaN、"" 都为真值</code></li><li><code>toPromitive: 如果检查该值是否有 valueOf 方法,看是否会返回原始值,如果返回值是原始值,则直接使用。否则,就使用 toString 方法,如果 toString 方法返回的是原始值,则直接使用,否则抛出 TypeError 错误。</code></li></ul></li><li><p>显/隐式强制类型转换:</p><ul><li><code>如果 + 运算符中其中一个操作数是字符串,则执行字符串拼接,否则执行加法运算</code>。</li><li><p><code>~(非) 运算符: ~ 会返回 2 的补码, 而 ~x 大致等于 -(x + 1)</code></p><pre><code class="js">// ~x 大致等于 -(x + 1)。
                
                ~42; // -(42 + 1) ==&gt; -43</code></pre></li></ul></li><li><p>|| 与 &amp;&amp;:</p><ul><li><code>|| 和 &amp;&amp; 操作符会对第一个操作数进行条件判断,且会对第一个操作数进行隐式类型转换(会通过 toBoolean 操作),然后再进行条件判断。</code></li><li><code>|| 运算符,如果条件判断结果为true, 就返回第一个操作数的结果。如果为 false, 就返回第二个操作数的结果</code>。</li><li><p><code>&amp;&amp; 运算符则相反,如果条件判断结果为 true 就返回第二个操作数结果,如果为 false, 就返回第一个操作数的结果</code>。</p><pre><code class="js">a || b;
                // 大致相当于
                a ? a : b;
                
                a &amp;&amp; b;
                // 大致相当于
                a ? b : a;</code></pre></li></ul></li><li><p>严格相等(===) 与宽松相等(==) 有一个重要的区别,特别是在判断条件上(<code>在于对操作数类型不同时他们的处理方式不同</code>):<code>== 允许在相等比较中进行强制类型转换,而 === 不允许</code>。</p><ul><li><code>在两个值类型相同情况下,使用 == 与 === 没有区别</code></li><li><code>在两个值类型不同情况下,就要考虑是否有没有强制类型转换的必要,有就用 ==, 没有就用 ===</code></li></ul></li><li><p>字符串与数字之间的比较规则:</p><ul><li><code>如果 Type(x) 为数字,Type(y) 为字符串,则返回 x == toNumber(y) 的结果</code></li><li><p><code>如果 Type(x) 为字符串,Type(y) 是数字,则返回 toNumber(x) == y 的结果</code></p><pre><code class="js">var a = 42;
                var a = "42";
                
                a === b; // false
                a == b; // true</code></pre></li></ul></li><li><p>其他类型与布尔值的比较规则:(宽松相等(==) 判断时两边的布尔值会进行 toNumber 操作)</p><ul><li><code>如果 Type(x) 是布尔类型,则返回 toNumber(x) == y 的结果</code></li><li><p><code>如果 Type(y) 是布尔类型,则返回 x == toNumber(y) 的结果</code></p><pre><code class="js">var a = "42";
                var b = true;
                
                a == b; // false</code></pre><pre><code class="js">var a = "42";
                // 不要这样用,条件判断不成立:
                if (a == true) {
                // ..
                }
                
                // 也不要这样用,条件判断不成立:
                if (a === true) {
                // ..
                }
                
                // 这样的显式用法没问题:
                if (a) {
                // ..
                }
                
                // 这样的显式用法更好:
                if (!!a) {
                // ..
                }
                
                // 这样的显式用法也很好:
                if (Boolean( a )) {
                // ..
                }</code></pre></li></ul></li><li><p>null 与 undefined 的比较规则:</p><ul><li><code>如果 x 为 null, y 为 undefined, 则结果为 true</code></li><li><p><code>如果 x 为 undefined, y 为 null, 则结果为 true</code></p><pre><code class="js">var a = null;
                var b;
                
                a == b;     // true
                a == null;  // true
                b == null;  // true
                null == undefined;  // true
                null == null;  // true
                undefined == undefined;  // true
                
                a == false; // false
                b == false; // false
                a == "";    // false
                b == "";    // false
                a == 0;     // false
                b == 0;     // false</code></pre></li></ul></li><li><p>所以我们<code>可将 null 和 undefined 作为等价来处理</code>。</p><pre><code class="js">var a = doSomething();
                
                if (a == null) {
                // ..
                }</code></pre></li><li><p>对象与非对象之间的相等比较规则:</p><ul><li><code>如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == toPromitive(y) 的结果</code></li><li><code>如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 toPromitive(x) == y 的结果</code></li></ul></li><li><p>宽松相等(==) 的假真值比较:</p><pre><code class="js">"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</code></pre></li><li>== null;             // false</li><li>== undefined;        // false</li><li>== NaN;              // false</li><li>== [];               // true -- 晕!</li><li><p>== {};               // false</p></li><li><p>如何安全使用 宽松相等(==) 操作符呢?</p><ol><li><code>如果两边的值有 true 或 false, 千万不要使用 ==;</code></li><li><code>如果两边的值有 []、""、0, 千万不要使用 ==;</code></li></ol></li><li>抽象关系比较存在隐式的强制类型转换,通常存在于 <code>a &lt; b, a = "" &gt; b 会被处理为 b &lt;&gt;</code> 判断中,其中一个很重要的点是,<code>会将结果反转</code>。</li><li><p>那<code>如何规避掉上述隐式的强制类型转换</code>?</p><ul><li><code>确保 ab 为相同的类型, 或进行显示的强制类型转换。</code></li></ul></li><li><p>如何让同时 a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3?</p><ul><li><p>其中不能用同时,因为 a = 1a = 2 之前执行,a = 2a = 3 之前执行。</p><pre><code class="js">// 方法一:
                var a = {
                i: 1,
                [Symbol.toPrimitive]() {
                return this.i++;
                }
                };
                if (a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) {
                console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
                }
                
                // 方法二:
                var a = {
                i: 1,
                valueOf() {
                return this.i++;
                },
                };
                if (a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) {
                console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
                }
                // 如果让 a.valueOf() 每次调用都产生副作用,比如第一次返回 1, 第二次返回 2,以此类推,就会产生这种情况。
                
                // 方法三:
                var a = {
                i: 1,
                toString() {
                return this.i++;
                },
                };
                if (a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) {
                console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
                }</code></pre></li></ul></li></ol><h2 id="item-0-9">特殊字符描述:</h2><ol><li>问题标注 <code>Q:(question)</code></li><li>答案标注 <code>R:(result)</code></li><li>注意事项标准:<code>A:(attention matters)</code></li><li>详情描述标注:<code>D:(detail info)</code></li><li>总结标注:<code>S:(summary)</code></li><li>分析标注:<code>Ana:(analysis)</code></li><li><p>提示标注:<code>T:(tips)</code></p><h2 id="item-0-10">往期推荐:</h2></li><li><a href="https://link.segmentfault.com/?enc=KniX8dyCu11Zz1HTRn2JnA%3D%3D.6S1TB67NU5ABuQefLYT3XgP0tBm%2BBMolORIVH9r6Dcx0KbPeOxTcRsFETJzLFK8hQ8apok2xsWRL8dS3Ov%2B%2F4w%3D%3D" rel="nofollow" target="_blank">前端面试实录HTML篇</a></li><li><a href="https://link.segmentfault.com/?enc=0GuMR6eINP2%2FFCE%2ByLvVLA%3D%3D.WghJDhveqYHvIWb79rbk1zTlSJDqJkHwZP%2F1ud3ZZZK%2FFJgIRGkON3rsni8loMSuhZgytq%2B2WYASjLGGUSsOHA%3D%3D" rel="nofollow" target="_blank">前端面试实录CSS篇</a></li><li><a href="https://link.segmentfault.com/?enc=aUGXxWce0haKXCpyMCaJog%3D%3D.9oArDiYAvI3457v99s5bezByphW9q7dozjAzLLTFHSvBDg%2BAekBTI5sNg3w9oudIkLr3SJYIBO2%2FE8AOTbVw7w%3D%3D" rel="nofollow" target="_blank">JS 如何判断一个元素是否在可视区域内?</a></li><li><a href="https://link.segmentfault.com/?enc=lDUN7RZcxJedA7zakh1LFw%3D%3D.KYn%2Ba2PSIdgXB7rW2MDrXCRYTFsKoc%2FIv%2FVkzQ1Ske%2B%2FEj0luZ6p5KgMcrWyQj4%2FCKv8Yq3%2BqCQsp8PwoahfLQ%3D%3D" rel="nofollow" target="_blank">Vue2、3 生命周期及作用?</a></li><li><a href="https://link.segmentfault.com/?enc=7hEnVIc6nBQbqF8ixH66ng%3D%3D.nTB%2FYCrP1hFpS3iTA6iXS097BjMGkj354DsxJKkSX%2FBtkuxks1OdNyI0PkxYUIlD3TR7A%2FcugpL%2FE55hFqs2fQ%3D%3D" rel="nofollow" target="_blank">排序算法:QuickSort</a></li><li><a href="https://link.segmentfault.com/?enc=wPDEtz491r4khPxofhCYfw%3D%3D.pUoFcpqcHc0zH1G28qUFgu%2FYCFEcPph%2F6Gtrk5Qpaj4dsJ6gx7gik%2FN%2Fp5UTYi6aOQ2YVcTfAMTw6x%2BdwqdWDg%3D%3D" rel="nofollow" target="_blank">箭头函数与普通函数的区别?</a></li><li><a href="https://link.segmentfault.com/?enc=NeUPheBe4xs0XSgXalvhrw%3D%3D.kBrLarXBZQ8Ua9yd7YsZipyc0tY%2BBXT3vBiBCqJBEG6jgKfGkTr%2FyXFP63ECUZt3%2FAsY1LlpaBPBu%2BjXXSrJtg%3D%3D" rel="nofollow" target="_blank">这是你理解的CSS选择器权重吗?</a></li><li><a href="https://link.segmentfault.com/?enc=nLUfnK3%2BNztxdrx32TmvCw%3D%3D.%2FzAR7A%2BzKaIqx0vgOMi4IsqVX22qsjM%2BQNgo%2BGquoX2hGgitghTG9z0mcbVP5mwVMLKbuybyzJp2s5AmNXvfNA%3D%3D" rel="nofollow" target="_blank">JS 中 call, apply, bind 概念、用法、区别及实现?</a></li><li><a href="https://link.segmentfault.com/?enc=7IgcsyZ6WqphNnhoqrJVQw%3D%3D.r2ux%2BrXaoNp5i9R8%2BKbqJvdU180g36S9h4uZQnhBfylsY9L5rPykgt1YbCXGLp8RvTp6AnBtcUtI379Q24Ymng%3D%3D" rel="nofollow" target="_blank">常用位运算方法?</a></li><li><a href="https://link.segmentfault.com/?enc=KQw59tBlwe4SLtIaR51Ong%3D%3D.bX3w2%2Fggc8%2BEFeuApMv1RiAPWwu1bInXBNvKeT5hmtdAZ%2BkQTZXHtcPQHPDUJ0kvzuDMfFOfq3QsIhC5fflLDA%3D%3D" rel="nofollow" target="_blank">Vue数据监听Object.definedProperty()方法的实现</a></li><li><a href="https://link.segmentfault.com/?enc=08j1ptQT1Cfda3AOGu%2BxUA%3D%3D.3MpyWq8PTOHNnYmETxqI4HwDCSau0gF1Wmla21yIMeeI4kC6VmKFKa1FCR0JiFQhmXRkdCGpYh8BDGD4Snd8nw%3D%3D" rel="nofollow" target="_blank">为什么 0.1+ 0.2 != 0.3,如何让其相等?</a></li><li><a href="https://link.segmentfault.com/?enc=YuNpOeiQr6F6f1xaxt2EaQ%3D%3D.9a%2FW0406QOqTZh3OMtHGpZSMz9etxMya%2FKa0x99vBnaGVtjKGtcQDCGseOcxVhG8kdhPH3LDy78mBx56BhHyFA%3D%3D" rel="nofollow" target="_blank">聊聊对 this 的理解?</a></li><li><p><a href="https://link.segmentfault.com/?enc=Nffu0YeETcApT2sisYGmhA%3D%3D.CQYq76VNbwGB2p%2B2UVo%2Fcl5oD9pvaUU86J4sYsQ7A0ZXL9t9Cd2OlBRLyZXmoTIDxKJ0izut3Tq948gPQQQeNA%3D%3D" rel="nofollow" target="_blank">JavaScript 为什么要进行变量提升,它导致了什么问题?</a></p><h2 id="item-0-11">最后:</h2></li><li>欢迎关注 <a href="https://link.segmentfault.com/?enc=nLJ53DbaXYvaDsdnf6SdpQ%3D%3D.BmTVuxAjF7FgoVKOmwQSa9xnZYdY41tIm%2BjtZa8l6Okb1yOBQGOQu%2FFCXu1rriBTJmwvhd7wAJ2W2GQjwzm6upiuyM2g6BKQVccFKPjmlmJDa%2Fi68EK6ahpd22RT0EP2iqG7iRJCGh6IU0GIlyHKdJ856L8%2BdINVijnjVKkPOxSlajIlBi7Nk%2BIkmsxIdbcjL7Mh0n0q4%2BseclmAnqVk3ZbZmDXCjhAA%2F%2B58us1WqZeEfoaeh8L0GLVoJ8QSI82HXjyhYcz3guXEx1w6flxqCDO%2FwZjNhgvVKbnIvApdVJs%3D" rel="nofollow" target="_blank">『前端进阶圈』</a> 公众号 ,一起探索学习前端技术......</li><li>公众号回复 <a href="https://link.segmentfault.com/?enc=rAR9%2FkE661aA%2FK6%2BWaVmgA%3D%3D.hkNHShGwn9mBu%2Fy1JAB7XF98pEMQtwup7glIho1eqXzAn%2BbOmRIz0EjrsZy6ouEuZAvgGGA%2B7V9q6wb5iExuisFcKJNAwUc68FIyGpVJak6tKGhjj1ABstvHGET1NN3cOHC22CJcTt40osgzhRo6N7HE46jBAgqzbQtx4OZ0o%2BNBdUf%2FIQHNX3JpOgj5zxzo94Aey4Tetn%2FtJPuBrCPT0ZXnavKa9NzoiLb6JMfeaRmHS9h2zlLMwSn7hl2377tA2mJC1x%2BYAf%2FgCNzzxSiZBDnpltFptyuzZ4FTIOOer0E%3D" rel="nofollow" target="_blank">加群</a> 或 <a href="https://link.segmentfault.com/?enc=eLp9V7DR7Y0o4o1Qm6WzMw%3D%3D.dABnY%2FligeQaiu8KHJgMi6fKumx5FfdcU2I1M2LZX9snQBRbO%2FtS05bdvjhoezpfYmvhDCX4BmMzSi8YmwY0oGnlOGGAplR5KPAZsMu2UtTXHPlvkRlh5ulMKunmfMtzLwOzK7a9%2BwSYXG%2BxmKCJKGS%2B35897oR%2FR2tQus97uHfNS8J8js6RpD8jIATscc7iSH%2BlizGAiaqqokPPdbC%2FLQe%2BgmB7jMGFZHaJ6T%2Frv8VQAm8L%2BQf8k4NZ4PpCS%2FOqmqy5yEt4EcK%2BNk%2F4I9Q6d0LC0LcVbyhDSNwjSyu3O5U%3D" rel="nofollow" target="_blank">扫码</a>, 即可加入前端交流学习群,一起快乐摸鱼和学习......</li><li>公众号回复 <a href="https://link.segmentfault.com/?enc=vthxT6SYUSjQ%2FWjfQp0PCA%3D%3D.qO%2BIctgmsTSHz1tp7jL%2BP0N6LwS7opFrdYQnFm%2FEoGNGOA87sytqwx5NJEJcZVVi0gqwrum54eY4c%2B8dS4HRR0Jfo%2FfxIH7xh3gsl4MNETIkg0NCKlnTdB4XDcCo5AfFKJ86kiqrTqGrYF%2BYfYW%2BCWYLCSyDVGDNPJtu7efDS8cDtzKCXWPOtUCxC%2BpvyFkA%2BsGKxYrSZKGGpeYexpSNuiiDTylltRBld1FUe%2FVloHgpY75kklL9D56pfXHB7jSrs3gnHIb8thOomVIC66o1RZKhmG5%2BfmnE2uGiaLaSUok%3D" rel="nofollow" target="_blank">加好友</a>,即可添加为好友<br></li></ol>