3

有趣的JS-隐式类型转换

 3 years ago
source link: https://segmentfault.com/a/1190000038752344
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

有趣的JS-隐式类型转换

当两个不同数据类型的操作数在做运算,或者操作数与操作符不匹配的时候,js引擎不会报错,会把操作数转成对应的数据类型继续执行下去,这个转换是自动完成的,经常被叫做隐式类型转换。其实大部分开发者都或多或少了解过这一点,比如我们经常会写这样的表达式!!param来确保参数是个Boolen值,+param确保是个数字,param + ''把参数转为字符串,但是总会遇到一些更复杂的表达式,例如:
{1} + 3
3 + [1, 2]
'1' + +'3'
false == ![]
等等一大堆,那只要把规则理清楚了,这些问题就迎刃而解了。主要就是这几种情况(忽略BigInt和Symbol这两种不常用的类型):

1. +符号运算

这个运算符比较特殊,先把它给拎出来,因为它既可以表示字符串连接符号用来拼接两个字符串,又可以作为算数运算符做加法运算。这取决于运算符两边的数据类型

  1. 只要有一个是字符串:把另外一个操作数的数据类型转成String按照字符串拼接
  2. 否则把两个操作数转成Number类型做加法运算

转换规则是重点,在介绍转换规则之前先说下JS的数据类型,数据类型分为简单数据类型和复杂数据类型,Undefined、Null、String、Number、Boolen这5个属于简单的简单数据类型,它们的换规则如下:

类型转Number转StringUndefinedNaN"undefined"Null0"null"Booleantrue => 1; false => 0true => 'true'; false => 'false'Number原样输出正数 => '正数'; 负数 => '负数'; NaN => 'NaN'; +0,-0 => '0'; Infinity => 'Infinity'; -Infinity => '-Infinity'; 科学计数法表示 => 对应的字符串; 非十进制数表示 => 十进制表示的字符串String基本是跟Number转字符串相反的,但是8进制数字字符串会转成去掉开头0后的10进制数,16进制数字字符串 => 10进制数字;其他不转为数字的会转为NaN原样输出

对象的转换就比较复杂了,不会进行直接转换而是通过一系列的方法。
转Number:

  1. 先调用它的valueOf方法,如果是简单类型则执行运算,返回结果
  2. 否则调用它的toString方法,如果是简单类型则执行运算,返回结果
  3. 否则抛出错误Uncaught TypeError: Cannot convert object to primitive value

转String,如果对象是日期Date类型

  1. 先调用它的toString方法,假如返回是简单类型则执行运算,返回结果
  2. 否则调用它的valueOf方法,如果是基本类型则执行运算,返回结果
  3. 否则抛出错误Uncaught TypeError: Cannot convert object to primitive value

如果不是日期类型:则1,2步骤互换,先调用valueOf再调用toString补充一点:如果是数组转成String会先调用join方法再调用toString

let obj = {};
obj.valueOf = function() {return 3};
obj.toString = function() {return '1'};

+obj // 输出 3
'1' + obj // 输出 '13'

obj.valueOf = function() {return {}};
+obj // 输出 1

let date = new Date();
date.valueOf = function() {return 3};
date.toString = function() {return 1};

+date // 输出 3
'1' + date // 输出 '11'

date.toString = function() {return {}}; 
'1' + date // 输出 '13'

ES6对象新加了Symbol.toPrimitive方法,假如对象存在这个方法则执行这个方法转换,没有的话执行上面的规则。toPrimitive方法只有一个参数,会根据传入的类型转成对应的结果,这里有一点需要注意:当作为字符串拼接的时候传入的参数是default,String(params)调用的时候参数才会传入string。对于Date类型应该是内部私有逻辑,试了下重写不管用,可以看下这个:https://tc39.es/ecma262/#sec-...

let obj = {};
obj.valueOf = function() {return 3};
obj.toString = function() {return '1'};

obj[Symbol.toPrimitive] = function(preferType) {
    if (preferType == "number") {
      return 666;
    }
    if (preferType == "string") {
      return '3';
    }
    return null;
}

+obj // 输出 666
'1' + obj // 输出 '1null',preferType是default
String(obj) // 输出 '3'

2. 关系运算符:>、<、==、===、>=、<=,!=

2.1相等运算符

对于===运算符除了比较值以外还会进行类型的比较,如果两边类型不一样肯定是不相等了
对于==号运算符:

  1. 如果两边都是引用类型(Object),存储的是内存地址,是不相等的([] == [] // false, {} == {} // false)
  2. 如果其中一个是null另一个是undefined返回true
  3. 如果其中一个是null或者undefined另一个不是null也不是undefined,返回false
  4. 如果其中一个是NaN,返回false
  5. Infinity除了与Infinity和+Infinity相比是true外,其他都是false
  6. -Infinity除了与-Infinity相比是true外,其他都是false
  7. 剩下的情况会把两边的操作数按上面提到的转Number的规则转成数字做比较,但如果两边的操作数都是字符串则按照从左到右依次按照它们的ASCII码值比较。例如'ac' < 'ab'结果是false,因为字母c的ASCII码值要大于字母b的ASCII码值
2.2其他关系运算符

按照2.1中规则7处理

3. 逻辑运算符(三目运算符、||、&&、!)、if和while条件表达式

转成Boolean值运算,除了这几个值undefined、null、0,+0、-0、''、NaN以外都是true

4. 算数运算符(*、/、-、%、++、--)和位运算符(|、&、~)

按照上面提到的转Number的规则,转成数字运算

除了前面几种主要的情况,就剩下一些特例了

  • alert,这个函数会把参数转为字符串
  • {开头的语句会被当做代码块,跟上后面的语句的时候相当于一目运算符,按照一目运算符的规则执行。例如:{} + 1 相当于 +1。如果想把它按对象执行的话需要加个小括号当作表达式执行({}) + 1,输出"[object Object]1"
  • null <= 0是true,null >= 0也是true。因为null > 0是false,所以nul <= 0,同理null >= 0

推荐几个链接:
https://tc39.es/ecma262/#sec-toprimitive
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive
https://interglacial.com/javascript_spec/a-11.html#a-11.6.1


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK