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

请先在纸上写出下面代码会输出什么开始我们今天的知识总结。

  console.log(new String('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类型

  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类型呢?因为采用函数表达式或函数声明时,实际上JS引擎在后面做了一个工作,相当于调用了new Function('参数1','参数2',''函数体),使用new创建的,肯定就是object类型了,至于typeof对函数返回的是function,这是一个特例。

二、JavaScript数据类型

JavaScript有六种基本数据类型:string、boolean、number、null、undefined、symbol(es6新添加),还有一种复杂数据类型:object

基本类型和object类型在内存中存储的方式是不一致的,基本类型数据存放在栈中(值类型),object类型(引用类型)在栈中会有一个指针(通常是函数名)指向堆中的数据。知道了这个知识点才知道==操作符的相关规则,才知道浅拷贝和深拷贝的坑。

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

数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 "" (空字符串)
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined 不适用 undefine

四、逻辑!的转换规则

使用该符号时,首先将操作符后面的数据转成布尔类型,然后再取反,没什么特殊的。这个运算符有一个很经典的应用。

var a;
var b=!!a;

这段代码的作用就是变量b的值只能为true或false,当a为undefined时b的值为false,否则为true

当然也可以采用var b=a || false来达到相同的效果,我个人更喜欢后面这种。

这背后的原理正是应用了上面第三大点的知识,所以说牢牢理解隐式转换是非常有必要的

五、相等比较==的规则

比较操作符会为两个不同类型的操作数转换类型,然后进行严格比较。当两个操作数都是对象时,JavaScript会比较其内部引用,当且仅当他们的引用指向内存中的相同对象(区域)时才相等,即他们在栈内存中的引用地址相同。

为了以防被博客上看到的一些总结误导,我特地查询了MDN上对类型转换规则的说明:

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

隐式类型转换调用的是valueOf或toString,转换结果请点击

从上面这段权威的规则可以得出以下结论:

1.如果两边类型不一致,但两边都属于string、number、boolean中的一种,则首先尝试将两边都转化成数字类型来比较(上面第1、2条总结起来)

  var a='123'
console.log(a==false) //false,'123'转成数字是123,右侧转成数字是0,最终比较123==0

2.如果一边是object类型,另一边是string或number(boolean类型也会转换成数字),则先看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

上面两条得出一个规律,相等比较就是把左边和右边转成number类型来比较

3.如果两边都是引用类型,不进行转换,直接比较内存中引用的是否是同一个地址。

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

4.null和undefined跟其它任何类型比较时,不做转换,也就是跟其它类型比较时结果总是为false,但undefined==null的结果总是为true

5.NaN跟任何类型比较都为false,NaN==NaN的结果也为false

搞清楚了上述规则,开始那几个题就特别简单了,而且万变不离其宗。

  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

六、大于或小于符的坑

先来看一个又会让你大吃一惊的例子

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

如果你回答是false,那么你又该看一看了,为什么会是true呢?因为字符串类型比较大小时,不是进行隐式转换,而是逐位比较ascii码,第1位不同则返回结果,否则继续比较第2位,直到某一位不同为止,上面的例子js引擎是这么来处理的

var a='23'.charCodeAt(0) //50

var b='3'.charCodeAt(0) //51

50<51 //false

比较大小时如果左右两边是变量,建议最好先转成数字来比较。

七、加号的转换

1.加号前后如果有一个为字符串的话,则把另一个转成字符串(js引擎在后面调用了toString()方法),然后做连接操作

2. 如果不满足上一条的话则转成数字,假设要转换的对象为obj,转换规则如下:

  • 如果obj为原始值,直接返回;
  • 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
  • 否则调用obj.toString(),如果执行结果是原始值,返回之;
  • 否则抛异常。
    true + 1 // 2,布尔型转化为了数字
    [] + 1 // '1',空数组转化为了字符串
    {} + 1 // '1',空对象转化为了字符串
    var now = new Date()
    typeof (now + 1) // 'string',日期对象转化为了字符串

0.1+0.2==0,3为false的坑

简单来说,产生这个问题的原因是因为js的数字类型只有一种,那就是浮点数

js中两个浮点数相加,如果结果不是整数,那么小数点后面默认还有很多位,这里有详细说明0.1 + 0.2 === 0.30000000000000004的背后

所以我们在类似的场景时,最好采用(0.1+0.2).toFixed(1)把小数点位数弄统一,再用parseFloat转换数字进行比较。

八、valueOf和stringOf

各类型调用valueOf或toString后的结果请点击

最新文章

  1. JavaScript 属性类型(数据属性和访问器属性)
  2. javascript中的call()和apply()方法的使用
  3. MySQL主从数据库同步
  4. Python文件格式化写入
  5. php操作mysqli(示例代码)
  6. Why Does Everyone Else Appear to Be Succeeding?
  7. 关于 typings install 的使用
  8. Oracle中四种循环(GOTO、For、While、Loop)
  9. 触发器(基本的SR触发器、同步触发器、D触发器)
  10. gulp详细入门
  11. 文件I/O实践(2) --文件stat
  12. Netflix公布个性化和推荐系统架构
  13. CentOS6.2(64bit)下mysql5.6.16主从同步配置
  14. Jquery常用的一些事件 keyup focus
  15. endnote插入文献的一些问题
  16. tp5生成小程序推广二维码
  17. 移动构造函数(c++11)
  18. java属性编辑器,即PropertyEditor
  19. ASP.NET MVC自定义验证Authorize Attribute(包含cookie helper)
  20. 剑指offer--45.二叉树的深度

热门文章

  1. Fy&#39;s dota2 题解
  2. [19/03/16-星期六] 常用类_Date时间类&amp;DateFormat类
  3. sql 查询表的字段数量
  4. umlの类图
  5. Mysql之inner join,left join,right join详解
  6. EF执行SQL语句
  7. 【转】RMAN删除过期备份或非过期备份
  8. Python函数中参数类型
  9. seajs简单使用
  10. #leetcode刷题之路1-两数之和