隐式类型转换

556 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

强制类型转换对比隐式类型转换会直观许多,转换的结果是显示的、符合转换的直观的,根据API可以很明确的知道转换结果,但是隐式类型转换就会隐喻很多,因为触发隐式类型转换存在多种方式,很容易在开发过程中忽视,导致经验不足的开发者而言较为晦涩难懂

隐式类型转换

如果遇到两个数据类型不一样,而需要进行逻辑运算符 (&&、 ||、 !)、运算符 (+、-、*、/)、关系操作符 (>、 <、 <= 、>=)、相等运算符 (==) 或者 if/while 条件的操作时,便会触发隐式类型转换,这也是导致常被忽略的原因之一

'+' 的隐式类型转换规则

'+' 号操作符,不仅可以用作数字相加,还可以用作字符串拼接。仅当 '+' 号两边都是数字时,进行的是加法运算。当使用 + 运算符计算 string 和其他类型相加时,都会转换为 string 类型进行拼接。其他情况,都会转换为 number 类型,但是 undefined 会转换为 NaN,相加结果也是 NaN

  • 如果其中有一个是字符串,另外一个是 undefined、null 或布尔型,则调用 toString() 方法进行字符串拼接;如果是纯对象、数组、正则等,将复杂类型将转换为基本类型(转换会存在优先级),再进行运算、拼接
  • 如果其中有一个是数字,另外一个是 undefined、null、布尔型或数字,则会将其转换成数字进行加法运算,对象的情况如上
  • 如果其中一个是字符串、一个是数字,则按照字符串规则进行拼接。
1 + 1;								// 2
1 + '1';							// '11'
1 + null; 						// 1
'1' + null; 					// '1null'
1 + undefined;				// NaN
'1' + undefined;			// '1undefined'
1 + true;							// 2
'1' + true;						// '1true'
1 + false;						// 1
'1' + false;					// '1false'

'==' 的隐式类型转换规则

在开发过程中使用频率非常高,其转换规则大致流程如下:

  • 如果类型相同,无须进行类型转换,直接判断是否相等

  • 如果其中一个操作值是 null 或者 undefined,那么另一个操作符必须为 null 或者 undefined,才会返回 true,否则都返回 false

null == undefined     // true
null == 1 			  		// false
undefined == 1    		// false
  • 如果其中一个是 Symbol 类型,那么返回 false
const symbol = Symbol()
symbol == 2 					// false
symbol == 'symbol'    // false
  • 两个操作值如果为 string 和 number 类型,那么就会将字符串转换为 number
'1' == 1          		// true
  • 如果一个操作值是 boolean,那么转换成 number
1 == true       			// true
11 == true       			// false
0 == true      				// false
  • 如果一个操作值为 object 且另一方为 string、number 或者 symbol,就会把 object 转为原始类型再进行判断

Object 的转换较其他类型会比较复杂一些,因为其中会牵扯到元编程的概念,但是Object的转行趋势就是从引用类型向基本类型数据转换,转换优先级如下:

首先会判断是否存在Symbol.toPrimitive,若是存在则优先调用并返回数据

若是不存在,则调用该对象上 valueOf 或 toString 这两个方法,该方法的返回值是转换为基本类型的结果

注意点:Symbol.toPrimitive返回的必须是基础数据类型,否则控制台会报错

const obj = {
  [Symbol.toPrimitive]() {
    return {
    }
  }
};

obj + 1;

上述代码会在控制台报错:Uncaught TypeError: Cannot convert object to primitive value

存在Symbol.toPrimitive方法

const obj = {
  [Symbol.toPrimitive]() {
    return 1
  },
  valueOf() {
    return 2;
  },
  toString() {
    return 3
  }
};

console.log(obj + 1); // 2

不存在Symbol.toPrimitive方法,只存在两种valueOf和toString方法时,对象倾向于转换成什么,就会优先调用哪个方法。如果倾向于转换为 Number 类型,就优先调用 valueOf;如果倾向于转换为 String 类型,就只调用 toString

倾向转向Number类型时,优先使用valueOf返回的数据,若valueOf返回的非基础数据类型将继续调用toString

valueOf返回的是基础数据类型

const obj = {
  valueOf() {
    return 2;
  },
  toString() {
    return '3'
  }
};

console.log(obj + 1); // 3, 走valueOf方法

valueOf返回的不是基础数据类型,那么将继续走toString方法

const obj = {
  valueOf() {
    // 返回非基础类型
    return {};
  },
  toString() {
    return '3'
  }
};

console.log(obj + 1); // '31' 走toString方法

倾向转换为String类型,优先使用toString返回的数据,若toString返回的非基础数据类型将继续调用valueOf

toString返回的是基础数据类型

const obj = {
  valueOf() {
    // 返回非基础类型
    return 2;
  },
  toString() {
    return '3'
  }
};

// 内容为 '3', 走toString方法
document.write(obj);
alert(obj) 

toString返回的是非基础数据类型,则会继续调用valueOf

const obj = {
  valueOf() {
    return 2;
  },
  toString() {
    // 返回非基础类型
    return {}
  }
};

// 内容为 2, 走valueOf方法
document.write(obj);
alert(obj) 

到了这里,读者会不会想着,若是toString和valueOf返回的都是非基础类型的数据,那么此时该是怎样呢?

答案是报错:Uncaught TypeError: Cannot convert object to primitive value