隐式转换比较是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后的结果请点击

js学习日记-隐式转换相关的坑及知识的更多相关文章

  1. Js 中那些 隐式转换

    曾经看到过这样一个代码:  (!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]] = sb , 你敢相信, ...

  2. Scala学习笔记--隐式转换

    隐式转换的规则:1.无歧义规则:隐式转换唯有不存在其他可插入转换的前提下才能插入  若编译器有两种方法修正x+y 如convert1(x)+y,convert2(x)+y,会报错2.单一调用规则:只尝 ...

  3. js中的隐式转换

    js中的不同的数据类型之间的比较转换规则如下: 1. 对象和布尔值比较 对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字 [] == true; //false [] ...

  4. js中的数据类型隐式转换的三种情况

    js的数据类型隐式转换主要分为三种情况: 1. 转换为boolean类型 2. 转换为number类型 3. 转换为string类型 转换为boolean类型 数据在 逻辑判断 和 逻辑运算 之中会隐 ...

  5. 大数据技术之_16_Scala学习_06_面向对象编程-高级+隐式转换和隐式值

    第八章 面向对象编程-高级8.1 静态属性和静态方法8.1.1 静态属性-提出问题8.1.2 基本介绍8.1.3 伴生对象的快速入门8.1.4 伴生对象的小结8.1.5 最佳实践-使用伴生对象解决小孩 ...

  6. JS的隐式转换 从 [] ==false 说起

    前言 最近和大创扯淡时说到了[] == false,从结果上来看我俩都答错了,从气势上来说我俩的歪理都能出书了(恩,程序猿的骄傲),但是这其实背后隐藏了一潭很深的水,对,很深... 隐式类型转换 JS ...

  7. Scala学习教程笔记三之函数式编程、集合操作、模式匹配、类型参数、隐式转换、Actor、

    1:Scala和Java的对比: 1.1:Scala中的函数是Java中完全没有的概念.因为Java是完全面向对象的编程语言,没有任何面向过程编程语言的特性,因此Java中的一等公民是类和对象,而且只 ...

  8. js数据类型隐式转换问题

    js数据类型隐式转换 ![] == false //true 空数组和基本类型转换,会先[].toString() 再继续比较 ![] == [] //true ![] //false [] == [ ...

  9. JS的类型转换,强制转换和隐式转换

    JS的类型转换 1.强制转换 通过String(),Number(),Boolean()函数强制转换 var str=123; var str1='123'; console.log(typeof s ...

随机推荐

  1. sting.Contains()方法

    作用: 返回一个值,判断给定的string对象 是否出现在指定的字符串当中 例子: if(str_arr[i].Contains(textBox1.Text)){//检查是否有匹配

  2. 行云管家V4.9正式发布:监控全面提升,首页、主机详情大幅优化,新增大量实用功能.md

    让大家久等啦!4.9版本中我们对监控模块进行了重构,在数据准确性与稳定性方面做了大幅提升.我们也对首页及主机详情页面做了大幅重构,以追求为您提供极致的用户体验.同时我们在新版本中增加了如:运维报表.用 ...

  3. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(六)之 好友申请、同意、拒绝

    不知道距离上一篇多久没有写了,可能是因为忙(lan)的关系吧.废话不多说,今天要介绍的不算什么新知识,主要是逻辑上的一些东西.什么逻辑呢,加好友,发送好友申请,对方审批通过,拒绝.(很遗憾,对方审批通 ...

  4. html或jsp页面自动提交,无需每次重启服务

    从eclipse转到idea遇到各种问题,之前eclipse可以自动保存页面内容无需重启服务,但是idea不可以,网上找了n种办法也没用,可能版本不一样吧,把我的解决方法纪录一下,方便以后有人遇到这个 ...

  5. 12java基础继承

    26.定义类Human,具有若干属性和功能:定义其子类Man.Woman: 在主类Test中分别创建子类.父类和上转型对象,并测试其特性.   package com.hry.test; public ...

  6. lucene&solr学习——创建和查询索引(代码篇)

    1. Lucene的下载 Lucene是开发全文检索功能的工具包,从官网下载Lucene4.10.3并解压. 官网:http://lucene.apache.org/ 版本:lucene7.7.0 ( ...

  7. mysql复制表数据,多表数据复制到一张表

    对于mysql 复制表数据可以使用 insert into select 方式 示例: $sql="insert into icarzoo.provider(providerId,provi ...

  8. JS JavaScript中的this

    this是JavaScript语言中的一个关键字 它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用. function test() { this.x = 1; } 上面代码中,函 ...

  9. Python基础—03-运算符与分支结构

    运算符与分支结构 运算符 赋值运算符 用'='表示,左边只能是变量 算术运算符 +.-.*:加.减.乘 /:除法运算,结果是浮点型 //:除法运算,结果是整型 %:求余 **:求幂 复合运算符 +=. ...

  10. Python基础—02-数据类型

    数据类型 存储单位 最小单位是bit,表示二进制的0或1,一般写作b 最小的存储单位是字节,用byte表示,1B = 8b 1024B = 1KB 1024KB = 1MB 1024MB = 1GB ...