当前位置: 首页 > news >正文

我的JS日记-隐式转换相关知识

隐式转换比较是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类型是引用类型,下面是存储示意图。

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

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

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

四、!的转换规则

使用该符号时,首先将操作符后面的数据转成布尔类型,然后再取反,没什么特殊的。这个运算符有一个很经典的应用,强制让一个变量的值只能为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将对象转换为其原始值(一个字符串或数字类型的值)。如果尝试转换失败,会产生一个运行时错误。
  4. 注意:当且仅当与原始值比较时,对象会被转换为原始值。当两个操作数均为对象时,它们作为对象进行比较,仅当它们引用相同对象时返回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.无法得到原始值,抛异常。

转载于:https://juejin.im/post/5b29a950e51d45588576e222

相关文章:

  • 流计算独享模式正式邀测
  • 微信小程序接口改版后的登录和会话保持流程
  • Spring Cloud Finchley 正式发布,包含 4 个重大更新!
  • React全家桶项目
  • Linux 配置双网卡绑定,实现负载均衡
  • Python程序的执行原理
  • 使用 node(wd)编写 Appium 测试用例--测试2
  • [BZOJ2850]巧克力王国
  • 刚刚!“跨境汇款”被区块链重新定义,马云:源于多年前一个承诺
  • 大数据理论体系总结--数据仓库管理与全链路数据体系
  • 局域网大型文件分发的可能解决方案
  • 搭建视频监控平台《监视我的团宝宝》
  • BCH踏着优化升级路线,在数字货币界声名鹊起
  • Rust 1.27支持SIMD
  • 使用机器学习预测电子竞技游戏《守望先锋》的胜负
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • 0x05 Python数据分析,Anaconda八斩刀
  • 11111111
  • codis proxy处理流程
  • Debian下无root权限使用Python访问Oracle
  • Git的一些常用操作
  • HTML-表单
  • jquery ajax学习笔记
  • js 实现textarea输入字数提示
  • Laravel核心解读--Facades
  • Otto开发初探——微服务依赖管理新利器
  • Spring Boot快速入门(一):Hello Spring Boot
  • 大数据与云计算学习:数据分析(二)
  • 关于使用markdown的方法(引自CSDN教程)
  • 你真的知道 == 和 equals 的区别吗?
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 如何用vue打造一个移动端音乐播放器
  • 深入 Nginx 之配置篇
  • 微信公众号开发小记——5.python微信红包
  • 微信小程序实战练习(仿五洲到家微信版)
  • 移动端唤起键盘时取消position:fixed定位
  • 最近的计划
  • 大数据全解:定义、价值及挑战
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​批处理文件中的errorlevel用法
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • (arch)linux 转换文件编码格式
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (Note)C++中的继承方式
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (独孤九剑)--文件系统
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • ***原理与防范
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .Net Redis的秒杀Dome和异步执行
  • .Net 路由处理厉害了