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

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

比较少见的情况

  • • 如何让同时 a == 1 && a == 2 && a == 3?
  • • 其中不能用同时,因为 a = 1 在 a = 2 之前执行,a = 2 在 a = 3 之前执行。
  • • 如下代码:
// 方法一:
var a = {
    i: 1,
    [Symbol.toPrimitive]() {
        return this.i++;
    }
};
if (a == 1 && a == 2 && a == 3) {
    console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
// 方法二:
var a = {
    i: 1,
    valueOf() {
        return this.i++;
    },
};
if (a == 1 && a == 2 && 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 && a == 2 && a == 3) {
    console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
  • • 在 == 隐式强制类型转换中最令人头疼的就是假值得相等比较。
"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
  • • 以上的这 24种情况 中有 17 中我们比较好理解,但有 7 中不好理解。
  • • 那如何安全使用 == 操作符呢?
  1. 1. 如果两边的值有 true 或 false, 千万不要使用 ==
  2. 2. 如果两边的值有 []、""、0, 千万不要使用 ==

抽象关系比较

  • • 在我们日常的代码中,可能会存在 a < b 这种情况的判断,但这里面也涉及了隐式强制类型转换,有必要要了解一下。
  • • 会发生隐式强制类型转换的算法只会针对于 a < b, a = "" > b 会被处理为 b <>
  • • ES5 规则:
  • 比较双方会首先调用 toPromitive,如果结果中出现非字符串,就根据 toNumber 的规则将双方强制类型转换为数字进行比较
// 如下:
var a = 42;
var b = "43";
a < b; // true 这里为什么会返回 true, 先保留疑惑,后面会解答
b < a; // false
// 再比如:如果比较双方都是字符串,则按照字母顺序进行比较:
var a = ["42"];
var b = ["043"];
a < b; // false
b < a; // true
// 再比如:如果比较双方都是字符串, 则会进行 toPromitive 操作
var a = {b: 42};
var b = {b: 43};
a < b; // false
b < a; // false
// 因为 a = [object Object], b 也是 [object, Object],所以按照字母顺序排序 a < b, b < a 不成立。
// 再比如:
var a = {b: 42};
var b = {b: 43};
a < b; // false
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
// 此时你可能会好奇 a < b 和 a == b 都是 false,为什么 a <= b 和 a > b 为 true?
// 因为根据规则 a <= b 会被处理为 b < a, 然后将结果反转。(如果没懂,回头看这段实例代码)
  • • 上面的结果可能与我们设想的大相径庭,相等比较有严格相等,关系比较却没有严格相等,也就是说如果要避免 a < b 之间的隐式强制类型转转,就只能确保 a 和 b 为相同的类型, 或进行显示的强制类型转换。

小结

  1. 1. 值类型转换规则:
  • toString: 对于普通对象来说,除非自定义,否则都会调用其内部的 toString() 方法。
  • toNumber: 在使用 Number() 或 toNumber() 方法将一个字符串转换为数字时,如果字符串中出现非数字字符,则会返回 NaN。
  • toBoolean: 除 undefined、null、false、+0、-0 和 NaN、"" 都为真值
  • toPromitive: 如果检查该值是否有 valueOf 方法,看是否会返回原始值,如果返回值是原始值,则直接使用。否则,就使用 toString 方法,如果 toString 方法返回的是原始值,则直接使用,否则抛出 TypeError 错误。
  1. 2. 显/隐式强制类型转换:
  • 如果 + 运算符中其中一个操作数是字符串,则执行字符串拼接,否则执行加法运算
  • ~(非) 运算符: ~ 会返回 2 的补码, 而 ~x 大致等于 -(x + 1)
// ~x 大致等于 -(x + 1)。
~42; // -(42 + 1) ==> -43
  1. 1. || 与 &&:
  • || 和 && 操作符会对第一个操作数进行条件判断,且会对第一个操作数进行隐式类型转换(会通过 toBoolean 操作),然后再进行条件判断。
  • || 运算符,如果条件判断结果为true, 就返回第一个操作数的结果。如果为 false, 就返回第二个操作数的结果
  • && 运算符则相反,如果条件判断结果为 true 就返回第二个操作数结果,如果为 false, 就返回第一个操作数的结果
a || b;
// 大致相当于
a ? a : b;
a && b;
// 大致相当于
a ? b : a;
  1. 1. 严格相等(===) 与宽松相等(==) 有一个重要的区别,特别是在判断条件上(在于对操作数类型不同时他们的处理方式不同):== 允许在相等比较中进行强制类型转换,而 === 不允许
  • 在两个值类型相同情况下,使用 == 与 === 没有区别
  • 在两个值类型不同情况下,就要考虑是否有没有强制类型转换的必要,有就用 ==, 没有就用 ===
  1. 2. 字符串与数字之间的比较规则:
  • 如果 Type(x) 为数字,Type(y) 为字符串,则返回 x == toNumber(y) 的结果
  • 如果 Type(x) 为字符串,Type(y) 是数字,则返回 toNumber(x) == y 的结果
var a = 42;
var a = "42";
a === b; // false
a == b; // true
  1. 1. 其他类型与布尔值的比较规则:(宽松相等(==) 判断时两边的布尔值会进行 toNumber 操作)
  • 如果 Type(x) 是布尔类型,则返回 toNumber(x) == y 的结果
  • 如果 Type(y) 是布尔类型,则返回 x == toNumber(y) 的结果
var a = "42";
var b = true;
a == b; // false
var a = "42";
// 不要这样用,条件判断不成立:
if (a == true) {
    // ..
}
// 也不要这样用,条件判断不成立:
if (a === true) {
    // ..
}
// 这样的显式用法没问题:
if (a) {
    // ..
}
// 这样的显式用法更好:
if (!!a) {
    // ..
}
// 这样的显式用法也很好:
if (Boolean( a )) {
    // ..
}
  1. 1. null 与 undefined 的比较规则:
  • 如果 x 为 null, y 为 undefined, 则结果为 true
  • 如果 x 为 undefined, y 为 null, 则结果为 true
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
  • • 所以我们可将 null 和 undefined 作为等价来处理
var a = doSomething();
if (a == null) {
    // ..
}
  1. 1. 对象与非对象之间的相等比较规则:
  • 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == toPromitive(y) 的结果
  • 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 toPromitive(x) == y 的结果
  1. 2. 宽松相等(==) 的假真值比较:
"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
  1. 1. 如何安全使用 宽松相等(==) 操作符呢?
  1. 1. 如果两边的值有 true 或 false, 千万不要使用 ==;
  2. 2. 如果两边的值有 []、""、0, 千万不要使用 ==;
  1. 2. 抽象关系比较存在隐式的强制类型转换,通常存在于 a < b, a = "" > b 会被处理为 b <> 判断中,其中一个很重要的点是,会将结果反转
  • • 那如何规避掉上述隐式的强制类型转换
  • 确保 a 和 b 为相同的类型, 或进行显示的强制类型转换。
  1. 1. 如何让同时 a == 1 && a == 2 && a == 3?
  • • 其中不能用同时,因为 a = 1 在 a = 2 之前执行,a = 2 在 a = 3 之前执行。
// 方法一:
var a = {
    i: 1,
    [Symbol.toPrimitive]() {
        return this.i++;
    }
};
if (a == 1 && a == 2 && a == 3) {
    console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
// 方法二:
var a = {
    i: 1,
    valueOf() {
        return this.i++;
    },
};
if (a == 1 && a == 2 && 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 && a == 2 && a == 3) {
    console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}

特殊字符描述


问题标注 Q:(question)答案标注 R:(result)注意事项标准:A:(attention matters)详情描述标注:D:(detail info)总结标注:S:(summary)分析标注:Ana:(analysis)提示标注:T:(tips)

相关文章
|
1月前
|
JavaScript 前端开发
JavaScript基础语法(类型转换)
JavaScript基础语法(类型转换)
33 0
|
7月前
|
JavaScript 前端开发
js中的类型转换
js中的类型转换
37 0
|
1月前
|
JavaScript 前端开发
JavaScript变量、数据类型、运算符及类型转换
JavaScript变量、数据类型、运算符及类型转换
47 0
|
1月前
|
JavaScript 前端开发 Python
javascript中的强制类型转换和自动类型转换
javascript中的强制类型转换和自动类型转换
|
1月前
|
JavaScript 前端开发 编译器
彻底理解JavaScript中的类型转换(上)
彻底理解JavaScript中的类型转换
120 0
|
1月前
|
JavaScript 前端开发
javascript中的类型转换
javascript中的类型转换
|
1月前
|
JavaScript 前端开发 算法
彻底理解JavaScript中的类型转换(下)
彻底理解JavaScript中的类型转换(下)
|
1月前
|
JavaScript 前端开发
【Javascript保姆级教程】显示类型转换和隐式类型转换
【Javascript保姆级教程】显示类型转换和隐式类型转换
101 0
|
1月前
|
JavaScript
JS 类型转换
JS 类型转换
28 0
|
7月前
|
存储 JavaScript 前端开发
Javascript数据类型和类型转换
Javascript数据类型和类型转换
34 0