千万别小看这些运算符背后的逻辑-阿里云开发者社区

开发者社区> 开发与运维> 正文

千万别小看这些运算符背后的逻辑

简介:

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


最近回顾javascript的一些基础知识点时,引起的思考确实颠覆了我之前的一些认知。我清楚地记得曾多次在网上看到一些奇奇怪怪的表达式,它们的运算结果着实让人懵逼。就比如我在js数据类型很简单,却也不简单这一篇笔记中提到的[] == ![]这样一个表达式,它的运算结果是true。如果你不细致地去研究它背后的运算逻辑,你只会惊呼”这是什么鬼“?相反,当你静下心来看清楚它的运算逻辑后,你会感叹“妙哉妙哉”!没错,本文的主角就是这些容易让人小觑的运算符。

加法运算符+

首先说的是加法运算符+,这是一个很容易被人忽视的运算符。我们知道,+可以用来做数字运算,也可以用作字符串拼接,但是还有一些细节可能是大家不知道的。如果+运算符的两个操作数类型不一致,或者说两个操作数既不是字符串也不是数字,那么它的运算规则是什么?
先举几个例子,你可以先思考下这些运算结果分别是什么。

1

其实规则很简单,我们只要简单地列举出数据类型的可能性,就几乎得到了完整的答案。

  • 如果操作数都是数字,进行数字的加法运算。
  • 如果操作数都是字符串,进行字符串的拼接。
  • 如果操作数是对象,会转换为原始值(一般是先调用valueOf(),日期对象比较特殊,会调用toString()),得到的原始值不再被强制转换为数字或字符串。在这种约束下,对象转为原始值基本都是字符串(如果你没有重写valuOf()或者toString()方法),根据下面的第四点,会执行字符串拼接操作。
  • 如果其中一个操作数是字符串,另一个操作数也会被转为字符串,+运算符执行字符串拼接操作。
  • 如果两个操作数都不是字符串或对象,则会进行算术加法运算(非数字的操作数会被强制转为数字)。

所以,不难得出上面列举的表达式的运算结果。

2

要记住这些规则并不简单,一个记忆技巧是:+运算符偏爱字符串拼接操作。

相等运算符==

这个运算符的运算规则,在js数据类型很简单,却也不简单这篇笔记中已经简单地解释过了。其实只要记住一条规则:对于==运算符,如果两个操作数是null或undefined,运算结果是true;否则,不管操作数的类型如何转换,==运算符最后都是数字的比较。

举几个简单的例子说明下:

3

比较运算符

大于>,大于等于>=,小于<,小于等于<=,用于比较数字的大小或字符在字母表中的排序。要注意的是,在ASCII中,大写字母排在小写字母前面。

这些比较运算符更偏爱数字的比较,除非两个操作数都是字符串。

对于字符串比较的情况,如果两个字符串的第一个字符是相同的,则会比较第二个字符,以此类推。

这里有一个比较特殊的NaN,它与任何值做比较都会返回false。

4

位运算符

位运算符很少用到,但是弄明白它们的运算逻辑是很有必要的。位运算符主要分为与&、或|、非~、异或^以及左移<<、带符号右移>>、无符号右移>>>等。

位运算符都是二进制的运算,并且是基于32位整数运算。所以十进制,十六进制的操作数都会先转为32位的二进制后再进行运算。这里以0x1234 & 0x00FF = 0x0034为例说明下流程:

  • 0x123转为二进制是0000 0000 0000 0000 0001 0010 0011 0100,0x00FF转为二进制是0000 0000 0000 0000 0000 0000 0011 0100。
  • 进行按位与操作,结果是0000 0000 0000 0000 0000 0000 0011 0100,最后转为十六进制就是0x0034。

移位运算符

在复习到移位运算符这块时,我不由得提出了一个疑问:“javascript中为什么没有无符号左移运算符?”要解答这样一个疑问,首先还是要看看左移和右移分别是怎么运算的。

摘取《计算机组成原理教程》书中的一段描述:

计算机中机器数的字长往往是固定的,当机器数左移n位或右移n位时,必然会使其n位低位或n位高位出现空位。那么,对空出的空位应该添补0还是1呢?这与机器数采用有符号数还是无符号数有关。对无符号数的移位称为逻辑移位,对有符号数的移位称为算术移位。

注意:在javascript中,移位运算符只支持移动0~31位,如果移动的位数超过了31位,位数会取模MOD 32。也就是说:

5

带符号右移>>

对于带符号右移(算术右移)运算而言,第一个操作数是有符号数,它的最高位代表符号位,在移位后的符号位不改变。简单总结就是“低位舍弃,高位补符号位”。

6

如果你自己写几个右移运算表达式做试验,你就会产生一个疑惑,为什么有的正数在带符号右移后却变成了负数,比如下面这个:

7

这是因为32位的最大带符号正整数是231 - 1,即2147483647,转换为二进制是0111 1111 1111 1111 1111 1111 1111 1111。正数的补码与原码相同,2147483648相当于在此基础上加1,就得到补码1000 0000 0000 0000 0000 0000 0000 0000,而这个补码是一个非常特殊的码,它没有对应的原码和补码,代表32位能表示的带符号数中最小的负数231 - 1,即-2147483648。而2147483648在32位带符号正数中是无法表示的,其值已经溢出了。

8

计算机只理解二进制,与人类所理解的十进制之间永远存在一个精度问题,需要足够的精度才能更加准确地表示十进制,而计算机的位数永远都是有限的,这就是矛盾存在的地方,所以会出现溢出这种现象。

就好比时钟一般,23时结束了又从0时开始。在带符号二进制表示法中,正数和负数首尾相连,形成一个环,在计算机可表示的范围内,溢出的那个数字在某种意义上能在另一个起点找到。

9

所以,下面的位运算表达式也是等价的:

10

无符号右移>>>

无符号右移也称为逻辑右移。无符号右移的移位过程中,符号位可能会改变。因此移位后,原来的负数可能变成正数。可以简单记忆为“低位舍弃,高位补0”。

12

左移<<

翻阅《计算机组成原理教程》可以发现,书中有描述到算术左移和逻辑左移。也就是说,左移也分带符号左移和无符号左移。经测试,javascript中的左移运算符<<一般不会改变符号位,意味着它是算术左移(其实对比<<和>>也能知道,<<是带符号左移)。

但是左移也要注意溢出的情况,比如:

13

那么为什么javascript中却没有逻辑左移呢?我找了一些资料,比如es5规范和注解,还有一些javascript的书籍,都没有找到解释。所以这里也没有一个权威的答案(如果有大佬知道的话,请不吝赐教)。

我个人的想法是,应该是要回到移位运算的本质。

二进制表示的机器数在相对于小数点作n位左移或右移时,其实质就是该数乘以或除以2n(n=1,2, …, n)。

而在左移过程中,如果把符号位都丢了,就失去了乘以2n的意义了。所以不只是javascript,其他编程语言如java等也没有逻辑左移运算符。

最后

不得不说,大学课程真的很重要。如果一直都保持对计算机基础课程的关注,相信理解这些编程语言背后的本质会变得轻松很多。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-06-05
本文作者:飞白丶
本文来自:“掘金”,了解相关信息可以关注“掘金”

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章