“一法三表”彻底记住JS显式/隐式强制类型转换

一、导读 由于各种历史原因javaScript的类型转换真的令人吐血。本文是老弟翻阅各种材料自己总结出的javaScript强制类型转换规则,整理了3张表和1个分析方法,便于记忆,小伙伴们可以先看结论,继续往下看分析有助于理解和记忆。 前言:首先要明白类型转换话题的范畴有多大?我们要讨论的是Boolean、String、Number、Array、Function、Date、Object这么多类型之间互相转来转去吗?当然不是,我们的目标只有2个:基本类型转成基本类型、复合类型转成基本类型。new String('hello')、new Number(1024)这种用装箱操作来把基本类型转成复合类型就不属于讨论的范畴(装箱甚至不能算是类型转换),再比如你让数字转成函数、数组转成函数本身就没意义。所以我们的目标就是基本类型转成基本类型、复合类型转成基本类型。 二、结论 一法三表,指的是记住三张表格里的特殊转换,其他情况全都可以用一法分析出来。 一法:基本法。 基本法是我按照ES5规范的ToPrimitive方案抽离出来的叫法。步骤是:任何类型(基本类型和复合类型)做转换时先检查自己是否有valueOf()方法,如果有并且返回基本类型值,就使用该值做强制类型转换,如果valueOf()返回的还是复合类型,那就放弃转去调用toString()方法,把toString()返回的值做强制类型转换。如果valueOf()和toString()都不返回基本类型(或不存在)就做不了类型转换并且报TypeError错。 三表之一:各类型toString()和valueOf()方法返回值对照表: 表一 三表之二:各类型转成基本类型对照表: 表二 三表之三:所有隐式类型转换情形发生时的分析方法: 表三 三、分析 1、表一的分析。valueOf()和toString()的作用很重要 基本法就是完全依赖这2个方法的。 (1)JS在Boolean.prototype、String.prototype、Number.prototype、Symbol.prototype、Function.prototype、Date.prototype、Array.prototype、Object.prototype上都分别定义了各自的valueOf()和toString()方法。 (2)拿上面let num = 12举例,我们知道它的原型链是下图这样的,也就是所有基本类型都能用Object.prototype的toString()和valueOf()两个方法,拿上面let num = 12举例,如果JS没在Number.prototype上覆盖定义toString()和valueOf()的话,那么num.toString()的结果就是"[object Number]"了。正是因为全部的基本类型都覆盖了这2个方法,才有了表一五花八门的结果。 Number类型变量的原型链 (3)可以看到,除了Date对象的valueOf返回了当前时间到1970-1-1日的毫秒数(跟date.getTime()的效果一样)以外,其他8个全部和Object的效果一致——返回对象本身,其中Srting、Number、Boolean、Symbol这4个本身就是基本类型。注意这里的valueOf全都是对象各自覆盖后了的,而不是通过原型链找到的Object.valueOf(),尽管返回的结果是一样的。 (4)相反,所有的对象不仅覆盖了toString方法,还彻底改变了返回值,Array对象就是所有元素调用自己的toString再拼接起来;Date对象就是调用toDateString()和toTimeString()再拼接起来;其他都是直接加个引号。 (5)let num = 12和let num = new Number(12),虽然两个不相等,但是他们的toString和valueOf返回值是一样的。因为new Number(12)起到的作用是装箱,它的核心还是12这个数字。 (6)let num = 12,num.toString()和Object.prototype.toString().call( num )肯定是不一样的。 (7)因为valueOf()返回的都是对象本身,所以开发者直接取对象变量就好了,基本不会去调用valueOf(),基本都是自动被引擎调用。 2、表二的分析 (1)首先必须清楚的是表二里“转Number”、“转String”和“转Boolean”指的是采用Number()、String()、Boolean()这三个全局函数显式转换的结果。 (2)先看转String那一列,会发现除了null、undefined两个,其他类型的显式转换全部可以按照基本法转换得到(调用自己toString()得出的结果),这说明了显式类型转换和隐式转换的结果保持了一致,这个肯定得一致,要不然程序员得疯掉。null和undefined有点特殊,用String(null)会得到“null”字符串。 (3)再看转Number那一列,会发现所有的复合类型转成Number也可以按照基本法转换得到,所以复合类型要转成数字,都是一律先转成字符串,字符串再转成数字。比如[1]要转成数字,按照基本法首先看valueOf()返回数组对象本身,数组不是基本类型,所以调用toString()得到字符串“1”,“1”再转成数字是1。所以剩下的只要记住这一列null、undefined、Boolean和String类型这4种转成Number的结果就很轻松了。 (4)再看转Boolean那一列,转Boolean很好记忆,记住只有false和undefined、null、+0、-0、NaN、"",这7个值会转成false,其他一律转成true。 3、表三的分析 表一表二列举出的是每种类型转成基本类型的结果。表三就是为了说明将要发生类型转换时JS时怎么转的,"1" == 1 类型不一致肯定要转成一样的再比较,那么会把“1”转成数字呢?还是把1转成字符串呢?还是谁在==左边就转成对方的类型呢? (1)单元+法、减乘除、取余、自增、自减这些只适用于数学计算,所以一律会转成数字。比如1-{age:30},{age:30}用基本法转成“[object Object]”再转成数字得到NaN,NaN+1还是NaN。 (2)==、!= 这两个宽松相等判断。==恐怕是面试官最爱问的了。看完这个分析再恶心的都是手到擒来。首先结论ES规范说明==除非中途两边类型一致了,否则最终都要转成数字。具体步骤是: 第1步:两边全部用基本法转成基本类型; 第2步:如果两边依然类型不一样,则统一转成数字再比较。 注意:用基本法转换后,不要管结果是什么,直接转成数字再比较。 比如 经典的[] == ![],!的优先级高于==,所以先算![],对照表二知道结果是false,所以变成判断 [] == false,用基本法转换[]转换成了“”(空字符串),变成判断“” == false,ok要转数字,对照表二,“”会转成0,false也转成数字0,所以最后 0==0 成立。 (3)双元+的情形要拎出来做典型,因为它可以用于数学加法和字符串拼接,比如1+2、“1”+“2”。所以双元+要看+号两边元素的类型。具体步骤是: 第1步:+号两边类型不一致,先用基本法转成基本类型; 第2步:如果+号两边的基本类型有一个是字符串,就把另一个转成字符串再拼接; 第3步:如果+号两边的基本类型都不是字符串,就都转成数字再相加。 比如1+{age:30},{age:30}用基本法转成“[object Object]”是字符串,所以把1转成“1”再拼接起来等于“1[object Object]”;再比如true+false,用基本法转一下,都得到自己,+号两边都是Boolean不是字符串,所以两边都转成数字,true转成1,false转成0,相加得到1。 (4)转Boolean类型的就对照表二就可以了。 4、特殊情况分析 (1)Date类型遇到双元+时。例如:let date = new Date(); let res = date + 1;实际的结果是输出"Fri Mar 20 2020 20:14:10 GMT+0800 (中国标准时间)1"。按道理这里date应该用基本法调用先调用valueOf()转换成基本类型1584670735713再做加法,但是没有,而是调用了toString()转换成了字符串再做拼接。 (2)Object.create(null)创建的对象做转换时。例如:let res = Object.create(null) + "hello"会报错Uncaught TypeError: Cannot convert object to primitive value。是因为用Object.create(null)创建的对象的原型是null,所以没有valueOf()和toString()方法,没法做类型转换。 (3)稀疏数组,即数组有空值时。看下面这个例子 var arr = [0]; arr[1] = undefined; arr[2] = null; arr[4]=4; 创建的数组是这样的[0,undefined,null,空值,3]。 arr.toString(); // "0,,,,4" String(arr); // "0,,,,4" String(arr[1]); // "undefined" String(arr[2]); // "null" String(arr[3]); // "undefined" 注意到,如果是直接调用数组的toString()或者用全局String()显式转换时,JS会把undefined、null、空值转成""空字符串参与拼接;而如果单独抠出来用String()做显式转换的话却会变成"undefined"、"null"和"undefined"。这还是值得注意的。 四、习题(做完不再怕面试) 小伙伴们可以按照本文一法三表的方法完成下面的习题,再去浏览器验证下。 1、[] + {} 2、{} + new Date() 3、[] - new Date() 4、{} + true 5、{} - false 6、[] + "" 7、[] + true + "2" + 3 - "99" 8 、null - undefined 8、null == false 9、null == undefined 10、undefined == false 11、true == "45" 12、false == 45 13、 "" == 0 14、 "" == false 15、0 == [] 16、0 == {} 17、"0" == 0 18、"0" == false 19、"0" == [] 20、[] == ![] 21、"" == [null] 22、2 == [2] 23、2 == ["2"] 24、1 == [true]

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):