隐式转换介绍
- 在js中,当运算符在运算时,如果两边数据不统一,CPU 就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算
- 这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换
- 例如
1 > "0"
这行代码在js中并不会报错,编译器在运算符时会先把右边的 "0" 转成数字 0 然后在比较大小。
隐式转换规则
- 转成 string 类型: +(字符串连接符)
- 转成 number 类型:++ --(自增自减运算符)+ - * / % **(算术运算符)> < >= <= == !=(关系运算符)
- 转成 boolean 类型:! !!(逻辑非运算符)
这里值得注意的 + ,它既是连接符,也是运算符。 1. 当 + 两边都有值,且至少一个值是字符串类型,就会出现字符串拼接。2. 当只有 + 后面有值,例如:
+"123"
等同于Number("123")
,会将字符串转换为数字123
字符串拼接
当 + 两边都有值,且至少一个值是字符串类型,就会出现字符串拼接。
如果 另一个一值是 对象 类型的,需要对该对象进行 隐式类型转换(后文会详细讲解,对象是如何进行隐式类型转换的)
举例:
console.log("" + null); // "null"
console.log("" + undefined); // "undefined"
console.log("" + 123); // "123"
console.log("1" + "23"); // "123"
console.log("" + [1]); // "1"
console.log("" + {}); // "[object Object]"
不同类型进行 比较 或 运算 时的隐式转换规律
如图,任意两种不同类型的值进行比较时,会按如图方式进行相应的类型转换,例如:对象和布尔比较的话,对象 => 字符串 => 数值,布尔值 => 数值。
那么问题来了,对象是如何转成字符串的,又是如何进一步转成数字的?
对象 进行数据类型转换的过程
- 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法(结果是undefined),目前已知的只有
new Date()
有这个方法, - 再调用对象的 valueOf 获取原始值,如果获取的不是原始值,
- 再调用对象的 toString 把其变成字符串
- 最后再把字符串基于 Number 转换为数字
看几个例子:
console.log([] + 1); // `[].valueOf()`没有原始值,再调用`[].toString()`得到`""`,字符串遇到`+`运算符可以进行拼接,就不需要转成数字 => "1"
console.log([2] - true); // `[].valueOf()`没有原始值,再调用`[].toString()`得到`"2"`,再调用`Number("2")`得到数字2。true直接调用`Number(true)`得到1。最后2 - 1 => 1
console.log({} + 1); // `{}.valueOf()`没有原始值,再调用`{}.toString()`得到"[object Object]",遇到`+` 号进行字符串拼接 => "[object Object]1"
console.log({} - 1); // `{}.valueOf()`没有原始值,再调用`{}.toString()`得到"[object Object]",再调用`Number("[object Object]")`得到NaN,最后NaN - 1 => NaN
其它类型转数字
Number(xxx) 和 +xxx 效果是一样
// 字符串转数字:只要遇到非有效数字字符,结果就为NaN
Number("") // 0
Number("123") // 123
Number("123x") // NaN
// 其它基础数据类型转换结果
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
// BigInt 类型会去除末尾的 n ,如果超过最大安全数字,就会采用科学计算法来进行显示
Number(10n) // 10
Number(100000000000000000000000000000000n) // 1e+32
/*
把对象转换为数字:
+ 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法(结果是undefined),目前已知的只有`new Date()`有这个方法
+ 再调用对象的 valueOf 获取原始值,如果获取的不是原始值,
+ 再调用对象的 toString 把其变成字符串
+ 最后再把字符串基于 Number 转换为数字
*/
Number([]) // 0
Number([2]) // 2
Number([2, 3]) // NaN
Number({}) // NaN
其它类型转布尔值
只有 0、NaN、null、undefined、空字符串、false 转为布尔类型时为 false,其余情况转换为布尔类型都是 true,而且没有特殊情况。
举例:
console.log(!0); // true
console.log(!!''); // false
多种数据类型进行比较运算(== 或 ===),得出下面这张图
结论
- 三个等号:只有值和类型都相等,才会相等,不会进行默认的数据类型转换。推荐使用
- 两个等号:如果两边的数据类型不同,首先要转换为相同的数据类型(转换的过程看前文),然后进行比较。
- 对象 == 字符串|数字,是把对象转换为字符串或数字,先后进行比较
- null == undefined,两个等号的情况下是成立的,除此之外,null 和 undefined 和除本身以外的任何值都不相等
- 对象 == 对象,比较的是堆内存地址,只有地址一样,结果才为 true
- NaN !== NaN,NaN和任何值都不相等,包括和他自己
- 除此之外,如果两边的数据类型不一样,全部统一转换为数字类型,然后进行比较
面试题:
第一题: [] == false;
和 ![] == false;
的输出结果是什么?为什么?
解答:[] == false
:首先是两个等号,两边的数据类型不一样,需要进行数据类型的隐式类型转换,按照转换规律:
1.先看 [] 的 Symbol.toPrimitive ,不存在的情况下,再调用 [] 的 valueOf,没有原始值,再调用 [] 的 toString,等到的值为空字符串,空字符串基于 Number 转换为数字 0 。
2.false 基于 Number 转换为数字 0。
3.0 == 0
,得到 true
![] == false
:![]需要先进行布尔值转换,得到 false, false == false
得到 true
第二题:
let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result);
解答:
注意:+undefined => NaN
100 + true => 101 + 21.2 => 122.2 + null => 122.2 + undefined => NaN + "Tencent" => "NaNTencent" + [] => "NaNTencent" + null => "NaNTencentnull" + 9 => "NaNTencent09" + false = "NaNTencentnull9false"
第三题:
var a = ?
if (a == 1 && a == 2 && a == 3) {
console.log("OK")
}
解答:
思路一:利用 == 的转化机制,来重写 Symbol.toPrimitive 或者 valueOf 或者 toString
- 重写 Symbol.toPrimitive
var a = {
i: 0
};
a[Symbol.toPrimitive] = function () {
return ++this.i
};
if (a == 1 && a == 2 && a == 3) {
console.log("OK");
};
- 重写 valueOf
var a = {
i: 0
};
a.valueOf = function () {
return ++this.i
};
if (a == 1 && a == 2 && a == 3) {
console.log("OK");
};
- 重写 toString
var a = {
i: 0
};
a.toString = function () {
return ++this.i
};
if (a == 1 && a == 2 && a == 3) {
console.log("OK");
};
思路二:利用数组的 shift 方法的特性
var a = [1, 2, 3]
// a[Symbol.toPrimitive] = a.shift;
// a.valueOf = a.shift;
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log("OK");
};
思路三:数据劫持
Object.defineProperty(window, 'a', {
get: function () { // 读取 window.a 属性时会触发 get 方法
this.xxx ? this.xxx++ : this.xxx = 1;
return this.xxx; // return 后面是给 window.a 赋的值
},
set(val) { // 给 window.a 赋值时会触发 set 方法
// val 是给 window.a 赋值时的那个值
}
});
if (a == 1 && a == 2 && a == 3) {
console.log("OK");
};