美文网首页
隐式转换相关要点

隐式转换相关要点

作者: 来自银河系 | 来源:发表于2018-06-19 22:42 被阅读0次

    隐式转换比较是js中绕不过去的坎,就算有几年经验的工程师也很有可能对这块知识不够熟悉。就算你知道使用===比较从而避免踩坑,但是团队其它成员不一定知道有这样或那样的坑,有后端语言经验的人常常会形成一个思维误区:“JS这门语言很简单,看看语法再找几个例子就能操作DOM,编写特效了”。随着react、vue、html5等技术在项目中大规模使用,越来越多的项目中使用了大量的JS,甚至整个项目都用JS来写(例如基于webapi的SPA管理后台、微信小程序、微信H5应用,Hybrid app),如果不深入的去学习JS,不改变思维误区,那么你的程序很有可能在这些地方产生BUG,这种隐藏的bug如果你对js不熟悉的话会非常难以查找。
    下面开始今天的讨论,请先在纸上写出你的答案。

    console.log(newString('abc')==true)
    console.log({}==true)
    console.log([]==![])
    

    如果您的答案是:true true false,那么恭喜你,你全部都答错了,你的思路可能如下:

    1.new String('123')创造出来的是一个字符串,非空字符串转成布尔值是true,true==true,结果自然为true
    2.{}是一个字面量对象,对象转换成布尔值是true,true==true,结果自然为true
    3.右侧有!运算符,优先级最高,先转右边。数组是一个对象,对象转换成布尔值结果是true,![]的结果为false,要比较的表达式变成了[]=false,然后数组转成布尔值为true,true==false,结果是false

    为什么你一个题都做不对呢?如果你很有可能是你没有掌握到js中对象的概念,null的特殊规则,以及==运算符的转换规则,下面是涉及到的一些知识。

    一、变量采用字面量形式、包装器方式,new 方式的区别

    var a='xxx'                  //申明的是一个string类型,它是一个基本类型
    var a=String('abc')          // String()是一个包装类,用于将参数转换成string类型
    var a=new String('abc')     //采用new方式时创建了一个object类型
    

    使用typeof验证上面的结论

    var a='abc'
    console.log(typeof a) //string
    console.log(typeof String('abc')) //string
    console.log(typeof(new String('abc'))) //object
    

    哪些类型是object类型呢?用new 方法创建出来的肯定是object类型,除此之外,字面量对象(即{ }) 数组、日期、正则表达式、Math、函数表达式、函数申明,都是object类型。
    有同学说不对呀,var a=function(){}和function a(){} 我用typeof输出时明明是function啊,你怎么说是object类型呢?为什么说函数申明或函数表达式是object类型呢,因为申明的时侯相当于调用了new Function('参数1','参数2',''函数体),使用new创建的肯定就是object类型了,至于typeof对函数返回的是function,这是一个历史遗留问题。

    二、JavaScript数据类型

    使用相等于符时基本类型和object类型的规则不同,所以我们有必要再次回顾一下js中的数据类型。
    JavaScript有六种基本数据类型:string、boolean、number、null、undefined、symbol(es6新添加),还有一种复杂数据类型:object
    按照数据在内存中的存储方式,可将数据类型划分为值类型和引用类型。值类型的数据是存在栈中的,而引用类型则是在栈中会有一个指针,比如对象名,函数名等,它指向堆中的数据。基本数据类型都是值类型,object类型是引用类型,下面是存储示意图。

    image
    知道了这个知识点后,以后遇到深拷贝问题就很好理解了。

    三、各种类型隐式转换到布尔类型对照表

    为什么要突然列这个呢,因为使用频率比较高,面试时经常会出现相关的题目,所以需要牢记。比如有一个变量a,我们需要判断变量a不为undefined时才执行代码,如果你不知道隐式转换,你很有可能会写成if(a===undefined),这是没问题的,关键是难得敲,而且多了几个字节出来。我这里直接摘抄了js高程设计上的表。


    convert.png

    四、!的转换规则

    使用该符号时,首先将操作符后面的数据转成布尔类型,然后再取反,没什么特殊的。这个运算符有一个很经典的应用,强制让一个变量的值只能为true或false

    var a;
    var b=!!a;
    

    这段代码的作用就是变量b的值只能为true或false,当a为undefined时b的值为false,否则为true,当然也可以采用var b=a || false来达到相同的效果,我个人更喜欢后面这种。
    这背后的原理正是上面的第三条,所以说牢牢理解隐式转换是非常有必要的

    五、==的转换规则

    比较操作符会为两个不同类型的操作数转换类型,然后进行严格比较。当两个操作数都是对象时,JavaScript会比较其内部引用,当且仅当他们的引用指向内存中的相同对象(区域)时才相等,即他们在栈内存中的引用地址相同。
    为了以防被博客上看到的一些总结误导,我特地查询了MDN上对类型转换规则的说明:

    1.当比较数字和字符串时,字符串会转换成数字值。 JavaScript 尝试将数字字面量转换为数字类型的值。
    2.如果其中一个操作数为布尔类型,那么先将布尔类型转换为数字类型。
    3.如果一个对象与数字或字符串向比较,JavaScript会尝试返回对象的默认值。操作符会尝试通过方法valueOf和toString将对象转换为其原始值(一个字符串或数字类型的值)。如果尝试转换失败,会产生一个运行时错误。
    注意:当且仅当与原始值比较时,对象会被转换为原始值。当两个操作数均为对象时,它们作为对象进行比较,仅当它们引用相同对象时返回true。

    官方的文档读起来总是有一些拗口,翻译成大白话再加上JS高程设计书上说的特殊情况,总结起来就以下几点:
    1.转换时如果两边都是引用类型,则直接比较内存中的地址(也就是指针指向的地址)

     console.log([]==[]) //false,指针指向的地址不同
    

    2.如果两边类型不一致,则两边都转成number类型,引用类型先调用valueOf()方法,如果能转成数字就OK,不能转成数字的话,就调用toString()转成字符串。

     var a='123'
      console.log(a==false)  //false,'123'转成数字是123,右侧转成数字是0,最终比较123==0
     console.log(a==123)  //true,右边是数字,直接转换左右即可
    

    object类型的比较

     var a=new String(123)
     console.log(a==123) //true,a.valueOf()结果就是数字123,最终比较的是123==123
    

    再来一个例子

    var a={} 
    console.log(a==1)
    //上面a==1在js解释引擎中的执行过程如下:
    //a.valueOf()获取到的不是基本类型,调用a.toString()得到'[object Object]'
    '[object Object]'==1;
    //两边类型不致,左侧转成数字
    NaN==1;//false,NaN跟任何类型比较都为false
    

    3.null、NaN、undefined和string、number、boolean、object类型比较时,都不做隐式转换,比较的结果直接为false。但是需要注意以下几个规则:

       console.log(NaN==NaN) //false
       console.log(undefined==null) //true
       console.log(null==null) //true
       console.log(null==undefined) //true
    

    搞清楚了上述规则,开始那几个题就特别简单了,而且万变不离其宗。我们一步一步的来分析一下。

      console.log(new String('abc')==true)
      //step1:右侧转成数字
      new String('abc')==1
      //step2 new String('abc').valueOf()不是数字也不是字符串,再调用toString()
      '[object Object]' ==1
     //step3:字符串转数字
      NaN==1 //false,NaN和任何类型比较都为false
    
      console.log({}==true)
      //step1:右侧转成数字
       {}==1
      //step2 {}.valueOf()不是数字也不是字符串,再调用toString()
      '[object Object]' ==1  
      //step3:字符串转数字
       NaN==1 //false,NaN和任何类型比较都为false
    
      console.log([]==![])
       //step1:!优先级比==高,先转右边,[]是对象类型,转成布尔值为true,!true就是false
       []==false
       //step2:右侧转成数字为0
       []==0
       //step3:左侧是一个对象,valueOf()转出来不是字符也不是字符串,调用toString(),得到空字符串
       ''==0
      //step4:字符串转成数字
      0==0 //true
    

    是不是还记不住规则,借用了一下高中政治书上的口吻,就很容易记住了:一个中心(左右两边转换成number为中心),两个基本点(转换条件:1.类型不同时才转换 2.两边都是引用类型时直接比较地址),一国两制(null、NaN、undefined使用一套制作,其它的使用另一套制度)

    六、大于或小于符

    先来看一个可能会让你条件反射般陷入思维误区的坑

     console.log('23'<'3') 
    

    如果你得出的结果是false,那么你脑子里极有可能对这个表达式进行了一个隐式转换,转成了数字,正确的结果是false,为什么呢?
    字符串类型比较大小时,不进行类型转换,而是逐位比较ascii码,第1位不同则返回结果,否则继续比较第2位,直到某一位不同为止。上面的例子中js引擎在背后做了如下规则(请忽视我把变量分成了两行写,我这里不考虑性能:))

    var a='23'.charCodeAt(0) //50
    var b='3'.charCodeAt(0) //51
    50<51 //false
    

    你可能会说哪个脑残会写这种代码,还真不一定,当<左右两边都是变量时就极有可能产生这种错误,另一个涉及到比较时的坑就是数组排序。

    var a=[1,10,3,100].sort()
    

    你期望的结果是[1,3,10,100],可是结果再次让你失望了,原因是sort()方法默认的比较规则会先把每个元素转成字符串,然后比较字符串的ascii码来确定先后顺序。

    七、+号规则

    +号运算符即可以对两个数相加,也可以连接字符串,那如果是[1,2,3]+4这种情况下又会发生什么呢?这就需要我们了解相应的规则,为了方便描述,我们把+号左侧的值叫做A,右侧的叫做B:
    第一步:如果A和B都是number类型,直接相加;
    第二步:接下来看A或B中是否有一个是否为string类型,如果有,则将另一个也转成字符串,然后连接
    第三步:既不是number,也不是string,则按如下规则转换:
    1.能转换成数字,返回之
    2.否则调用valueOf(),如果执行结果是基本类型,返回之;
    3.否则调用toString(),如果执行结果是基础类型,返回之;
    4.无法得到原始值,抛异常。


    valueOf和toString参考.png

    相关文章

      网友评论

          本文标题:隐式转换相关要点

          本文链接:https://www.haomeiwen.com/subject/twjzeftx.html