隐式转换相关要点

隐式转换比较是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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,102评论 0 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • Javascript 中有6种基本类型(包括 symbol),以及对象类型,他们在不同的运算中会被系统转化为不同是...
    faremax阅读 810评论 0 3
  • 我不说并不代表我不在乎,我不提并不代表我已释怀,我清楚成年人的世界没有容易二字,也了解什么叫如人饮水冷暖自知,更明...
    卓剑海阅读 151评论 0 0
  • 1.When to use NSMutableArray and when to use NSArray? 什么时...
    IT_攻城师阅读 875评论 0 16