js中的 隐式数据类型转换

隐式转换介绍

  • 在js中,当运算符在运算时,如果两边数据不统一,CPU 就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算
  • 这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换
  • 例如1 > "0"这行代码在js中并不会报错,编译器在运算符时会先把右边的 "0" 转成数字 0 然后在比较大小。

隐式转换规则

  1. 转成 string 类型: +(字符串连接符)
  2. 转成 number 类型:++ --(自增自减运算符)+ - * / % **(算术运算符)> < >= <= == !=(关系运算符)
  3. 转成 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]"

不同类型进行 比较 或 运算 时的隐式转换规律

如图,任意两种不同类型的值进行比较时,会按如图方式进行相应的类型转换,例如:对象和布尔比较的话,对象 => 字符串 => 数值,布尔值 => 数值。
那么问题来了,对象是如何转成字符串的,又是如何进一步转成数字的?

对象 进行数据类型转换的过程

  1. 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法(结果是undefined),目前已知的只有new Date()有这个方法,
  2. 再调用对象的 valueOf 获取原始值,如果获取的不是原始值,
  3. 再调用对象的 toString 把其变成字符串
  4. 最后再把字符串基于 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

多种数据类型进行比较运算(== 或 ===),得出下面这张图

结论

  1. 三个等号:只有值和类型都相等,才会相等,不会进行默认的数据类型转换。推荐使用
  2. 两个等号:如果两边的数据类型不同,首先要转换为相同的数据类型(转换的过程看前文),然后进行比较。
    • 对象 == 字符串|数字,是把对象转换为字符串或数字,先后进行比较
    • 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

  1. 重写 Symbol.toPrimitive
  var a = {
    i: 0
  };
  
  a[Symbol.toPrimitive] = function () {
    return ++this.i
  };

  if (a == 1 && a == 2 && a == 3) {
    console.log("OK");
  };
  1. 重写 valueOf
  var a = {
    i: 0
  };

  a.valueOf = function () {
    return ++this.i
  };

  if (a == 1 && a == 2 && a == 3) {
    console.log("OK");
  };
  1. 重写 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");
  };
posted @ 2021-04-12 10:19  真的想不出来  阅读(1664)  评论(0编辑  收藏  举报