隐式转换规则
前言:
涉及隐式转换最多的两个运算子 + 和 ==。
+运算子即可数字相加,也可以字串相加。
== 不同于===,故也存在隐式转换。
减、乘、除这类运算子只会针对number型别,故转换的结果就是转换成number型别。
转换表
规则:
第一步:决定该转换成什么类型:
加号(+)专属规则:
当一侧为String类型,加号(+)会被定义为字元串接,并会让另一侧优先转换为字串。当一侧为Number类型,另一侧为原始类型(primiary type),原始类型会转换为Number型。当一侧为Number类型,另一侧为引用类型(reference type:Array , Function , Object ),引用类型(一侧)和Number型(另一侧)皆转换成字串后串接。优先顺序为由上而下!
第二步:需转换侧的值该如何转。
将值转为原始值,ToPrimitive():js引擎内部的抽象操作ToPrimitive有着这样的签名:ToPrimitive(input, PreferredType?)
input是要转换的值,PreferredType是可选引数,可以是Number或String型别,由第一步决定。
2-1. PreferredType是Number型(第一步决定该转换为Number型)时:
如果输入的值已经是一个原始值,则直接返回它否则,如果输入的值是一个物件,则呼叫该物件的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值。否则,呼叫这个物件的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。否则,丢掷TypeError异常。2-2. PreferredType是String型(第一步决定该转换为String型)时:
如果输入的值已经是一个原始值,则直接返回它否则,呼叫这个物件的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。否则,如果输入的值是一个物件,则呼叫该物件的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值。否则,丢掷TypeError异常。2-3. PreferredType在第一步没有办法指定转换哪一种型时:
该物件为Date型别,则PreferredType被设定为String,并重複2-1的流程。补充:(前端工程研究:关于 JavaScript 中 Date 型别的常见地雷与建议作法)否则,PreferredType被设定为Number,并重複2-2的流程。3-1.valueOf()方法:
Number、Boolean、String这三种建构函式生成的基础值的物件形式,通过valueOf转换后会变成相应的原始值。Date这种特殊的物件,其原型Date.prototype上内建的valueOf函式将日期转换为日期的毫秒的形式的数值。除此之外返回的都为this,即物件本身。var num = new Number('123');num.valueOf(); // 123var str = new String('12df');str.valueOf(); // '12df'var bool = new Boolean('fd');bool.valueOf(); // truevar a = new Date();a.valueOf(); // 1515143895500var a = new Array();a.valueOf() === a; // truevar b = new Object({});b.valueOf() === b; // true
3-2.toString:
Number、Boolean、String、Array、Date、RegExp、Function这几种建构函式生成的物件,通过toString转换后会变成相应的字串的形式。其他物件返回的都是该物件的型别。参考转换表。第三步:原始值转换。
经过前面步骤,会得到一个原始值,之后依照运算子的需求选择利用ToNumber将该原始值转换成数自型态;或是利用ToString将该原始值转换为字串,转换规则参考顶层的转换表。
试解:
第一题:
({} + {}) = ?
两个物件的值进行+运算子,肯定要先进行隐式转换为原始型别才能进行计算。
1、第二步:ToPrimitive转换,由于没有指定PreferredType型别,{}会使预设值为Number,进行ToPrimitive(input, Number)运算。
2、所以会执行valueOf方法,({}).valueOf(),返回的还是{}物件,不是原始值。
3、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
故得到最终的结果,"[object Object]" + "[object Object]" = "[object Object][object Object]"
第二题:
2 * {} = ?
1、首先乘号运算子只能对number型别进行运算,故第一步就是对{}进行ToNumber型别转换。
2、由于{}是物件型别,故先进行原始型别转换,ToPrimitive(input, Number)运算。
3、所以会执行valueOf方法,({}).valueOf(),返回的还是{}物件,不是原始值。
4、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
5、转换为原始值后再进行ToNumber运算,"[object Object]"就转换为NaN。
故最终的结果为 2 * NaN = NaN
严格相等(===)
定义:比较两侧的值是否在型态上及数值上皆相同。
NaN === NaN; //false+0 === -0; //true
宽鬆相等(==)
定义:宽鬆相等不限于型态相同,即型态不相同也可宽鬆相等。
1 == '1' // true
运作方式:
一侧为boolean值的宽鬆相等比较:值首先会被转换为数字类型,根据boolean类型的ToNumber规则,true转为1,false转为0。数字类型和字串类型的宽鬆相等比较:
当数字类型和字串类型做宽鬆相等比较时,字串类型会被转换为数字类型;根据字串的ToNumber规则,如果是纯数字形式的字符串,则转为对应的数字,空字符转为0, 否则一律按转换失败处理,转为NaN。
注意NaN不与任何值相等,包括它自己。
注意字串内如果是16进位制,也算纯数字。当引用型别和原始型别的宽鬆相等比较:
对象类型会依照ToPrimitive规则转换为原始类型
试解:
第一题:
[2] == 2 // true
解:
属于引用型别和原始型别的宽鬆相等比较。[2]做ToPrimitive操作,因为[2]非data型(2-3.),所以PreferredType是Number型[2]也就是先调用valueOf,回传仍是[2]。[2]再调用toString,回传是'2'。'2'已经符合数字类型和字串类型的宽鬆相等比较之条件,ToNumber变成2。第二题:
[] == !{} // true
1、! 运算子优先顺序高于==,故先进行!运算。
2、!{}运算结果为false,结果变成 [] == false比较。
3、属于一侧为boolean值的宽鬆相等比较:
等式右边y = ToNumber(false) = 0。结果变成 [] == 0。
4、属于当引用型别和原始型别的宽鬆相等比较:
[]会先呼叫valueOf函式,返回this。
不是原始值,继续呼叫toString方法,x = [].toString() = ''。
故结果为 '' == 0。
5、属于数字类型和字串类型的宽鬆相等比较:
等式左边x = ToNumber('') = 0。
所以结果变为: 0 == 0,返回true,比较结束。
参考文章:
IT人-javascript 隐式转换:https://iter01.com/472967.html程式前沿-从一道面试题说起—js隐式转换踩坑合集:https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/701003/#outline__1JavaScript 隐式类型转换,一篇就够了!:https://chinese.freecodecamp.org/news/javascript-implicit-type-conversion/