JavaScript 运算符

一、JS中的逻辑或 || 、逻辑与 &&

首先如果第一项的值不是Boolean值的话,会转为Boolean类型的

在数值上,0和-0在转为Boolean时为false,其余为true

Ⅰ- 问题提出

我在平时写js代码时,一般也就使用&&和||判断true和false

但是最近在看其他人代码与源码时,发现出现了大量逻辑或 || 、逻辑与 && 的其他用法,于是在此记录梳理一下

Ⅱ - 逻辑或 ||

console.log(1 || 2);  //真或真   1
console.log(1 || 0); //真或假   1
console.log(0 || 2); //假或真   2
console.log(-1 || 2); // 注意:只有0代表假,负数不为假,所以仍是真或真  -1
console.log(0 || false); //假或假  false
console.log(false || 0); //假或假  0
console.log(true || false); //布尔值的真或假  true
console.log(false || true); //布尔值的假或假  true

逻辑或 || 规律总结:

  • || 前方为true: 则返回运算符前方内容或运算结果;
  • || 前方为 false: 则返回运算符后方的内容或运算结果
  • || 当第一位判断到为真,结果就已经是,所以不用再走到运算符后方,只要将前方内容返回即可(true),反之相反

Ⅲ - 逻辑与 &&

console.log(1 && 2);  //真并真   2
console.log(1 && 0); //真并假    0
console.log(0 && 2); //假并真    0
console.log(-1 && 2); // 注意:只有0代表假,负数不为假,所以仍是真或真   2
console.log(0 && false); //假并假  0
console.log(false && 0); //假并假  false
console.log(true && false); //布尔值的真或假   false
console.log(false && true); //布尔值的假或假   false

逻辑与 && 规律总结:

  • && 前方为 true: 则返回运算符后方内容或运算结果;
  • && 前方为 false: 则返回运算符前方的内容或运算结果
  • 因为 true && false 为 true,所以当运算符前方为 true 时,就靠的后方内容真假为最终结果,所以可以直接返回后方的内容作为运算结果,反之相反

Ⅳ - 应用场景举例

转换成数组时根据特定条件去重
let andObj = {}
let orObj = {}
let arr = ['努力学习的汪','努力学习的汪','hongjilin','努力学习的汪'] 
//使用&& 运算符
arr.map((item,index)=>{
   !andObj[item] && (andObj[item]=index)  //当此属性名第一次出现时,赋予obj
})
//使用 || 运算符,其实判断就恰好与&&相反
arr.map((item,index)=>{
   andObj[item] || (andObj[item]=index)  //当此属性名第一次出现时,赋予obj
})
console.log(andObj) // {努力学习的汪: 1, hongjilin: 2}
console.log(orObj) // {努力学习的汪: 1, hongjilin: 2}

这里只是举个偏例,实际上可以变换成很多适用场景

二、运算优先级

Ⅰ- 以题入手

此部分我觉得仍是从各种题目入手,通过分析题目对此部分知识点进行梳理整合

如果要看更多JS面试题目可以点这里 --> JavaScript专项练习

1、求下方打印结果

var a = 18;
var b = "努力学习的汪";
var c = false;
var d = a && b || c ? c || b ? a : c && b : a ;
console.log(d)
知识点梳理以及答案解析
  1. && 运算符的优先级高于 ||,而 || 的优先级又高于? :
  • 所以我们对它进行第一次分解:((a && b) || c) ? (c || b) ? a : (c && b) : a
  1. ? : 是右关联
  • 例如: a ? b : c ? d : e --> a ? b : (c ? d : e)
  • 所以进一步对上方进行分解 : ((a && b) || c) ? ((c || b) ? a : (c && b)) : a
  1. 对于&&来说,如果条件判断结果为true就会返回第二个操作数的值,如果为false就会返回第一个操作数的值
  • 此知识点在本人笔记 [一、JS中的逻辑或 || 、逻辑与 &&](#**一、JS中的逻辑或 || 、逻辑与 && **) 部分已经给出了详细解释
  • 所以对上方式子进行初步运算后可以得出: c ? ((c || b) ? a : c) : a
  1. 对于 || 来说,如果条件判断结果为true就会返回第一个操作数的值,如果为false就会返回第二个操作数的值;
  • 此知识点在本人笔记 [一、JS中的逻辑或 || 、逻辑与 &&](#**一、JS中的逻辑或 || 、逻辑与 && **) 部分已经给出了详细解释
  • 所以对上方式子进行初步运算后可以得出: c ? (b ? a : c) : a
  1. 此时结果就非常明了了,下面给出结果变化
  • c ? (b ? a : c) : a ==> false ? ( “努力学习的汪” ? 18 : false) : 18
  • 经过隐式转换后 : false ? (true ? 18: false) : 18
  • 三元运算符可知,当**?前为false**,返回**😗*后方的值 所以结果为18

三、常见的坑

此处列举几个常见的浮点型运算的坑

  1. 著名的 0.1 + 0.2 === 0.30000000000000004
  2. 1000000000000000128 === 1000000000000000129
  3. 0.7 * 180 == 125.99999999998
  4. 其他

四、IEEE Floating Point Standard (IEEE754浮点数表示法标准)

在正式开始说JS浮点型运算前,我们要先了解一个东西,那就是: IEEE754浮点数表示法标准 此处只要大致看一下就行,有个概念

因为JS的浮点就是按照 IEEE754浮点数表示法标准 进行的

此部分借鉴资料: 思否的IEEE754浮点表示法详解;简书的IEEE754表示浮点数;百度文库的IEEE754标准浮点数表示法;IEEE 754浮点数标准详解;实际上这应是大学时期学的,只是可能你忘记了

Ⅰ - 组成

IEEE754标准包含一组实数的二进制表示法。它有三部分组成:

  • 符号位
  • 指数位
  • 尾数位

三种精度的浮点数各个部分位数如下:

精度符号阶/指数尾数总位数
单精度182332
双精度1115264
长双精度1156480
分类:

image-20210920103506056

Ⅱ - 正规化

对于将某个实数表示为计算机浮点数,首先要将其正规化,也就是表示为形如:

image-20210920092848998

的样子。其中 [b] 是 01 ,而 [p] 二进制数表示的指数位。这样,假设想表示为单精度(float)的浮点数,那么:

  • 第一位符号位用 0 表示正,用 1 表示负
  • 将指数 [p] 加上 移码 表示为8位的二进制数
  • 在接下来的23位填充位数 [b] 部分。由于正规化表示时,最左边部分总是 1 ,所以我们只需表示23位的尾数即可。

1、移码

上述中有一个词:移码(exponential bias),相信很多同学都不知道是什么.

因为指数 [p] 有正有负,那么在8位的指数位中我们就要拿出第一位来指示符号,这样显然会造成不必要的浪费。给指数加上移码,就能保证结果总是一个非负数,也就可以将8个指数位都利用起来。对于有 [M] 个指数位的精度,其移码为:

image-20210920093853995

这样就能得到上方三种精度的移码:

精度M(阶/指数位数)移码二进制表示
单精度81270111 1111
双精度111023011 1111 1111
长双精度1516383011 1111 1111 1111

以双精度(Double)的为例。双精度的指数位有11位。这样可以表示的数是从000 0000 0000111 1111 1111,也就是 指数加移码 所表示的范围从0到2047,那么,减去移码1023,则可以表示的指数是-1023到1024。但是注意,-1023和1024作为他用(后面会说到)。所以实际上能表示数的指数是从-1022到1023。

2、单精度、双精度、长双精度

这边可能会有基础比较差的同学会问单精度双精度这是啥,其实这个就是很基础的知识点了,在很多变成课本或者书籍上都有记录,这边就不深入讨论,而从使用的角度来讲,我想大体了解以下的情况就可以了:

  1. 整数、长整数: 都是相当于数学中的整数,只是由于计算机中存放数据的内存空间有限,所以计算机中的整数和长整数都有一定的范围,而且因计算机硬件的不同而不同,但一般而言存放长整数的内存空间比整数的要多,所以表示的范围也更大。
  2. 单精度浮点数、双精度浮点数: 都是计算机用来表示实数的方法,与上边的整数、长整数类似,也有一定的表示范围,而且相应的双精度浮点数比单精度浮点数的范围更大、而且有效位数也更多,也即更精确。
  3. 如果在程序中用一个变量表示一个人的年龄,那么由于人的年龄都是整数,所以这个变量定义为整数类型的最合适了。如果要用一个变量表示一件物品的价格,由于价格是实数,比如66.60元,那么要用单精度浮点数。
  4. 如果变量的范围要比整数、单精度浮点数大,或者精度比单精度浮点数要高,那么就要改用长整数或者双精度浮点数。
  5. 长双精度顾名思义就是比双精度更长,更精确
  6. 以上的数的范围和精度都可以在很多课本和手册中查到。

3、举些栗子

① 【例】:0.1与0.2转化为二进制表示

这边以图文方式举例:

image-20210920100244664

②【例】:求3.14的单精度浮点数表示。

首先将3.14转成二进制:

  • 整数部分3的二进制是11b
  • 小数部分0.14的二进制是:0.0010001111010111000010[10001111…]b(方括号中表示小数点后第23位及之后)。

这样,3.14的二进制代码就是:11.0010001111010111000010[10001111…]×20b,那么用正规化表示就是:1.10010001111010111000010[10001111…]×21b

方括号表示的就是小数点后第24位了,由于单精度浮点数尾数只有23位,所以需要舍入(舍入方法见后):由于第24位为1,且之后 不全为 0,所以需要向第23位进1完成上舍入:1.10010001111010111000011×21b
而其指数是1,需要加上移码127,即128,也就是(1000 0000)b
它又是正数,所以符号为0。
综上所述,3.14的单精度浮点数表示为:
0 1000-0000 1001-0001-1110-1011-1000-011b
十六进制代码为:0x4048F5C3

通过此例可知,3.14的单精度浮点数表示是0 1000-0000 1001-0001-1110-1011-1000-011。现在我们来还原,看看它的误差:

  • 指数是128,那么还原回去(减去移码),实际指数就是1
  • 尾数还原也就是:10010001111010111000011b,所以是:1.10010001111010111000011×21b,也就是11.0010001111010111000011b

利用二进制转十进制,可得它对应的十进制数是:3.1400001049041748046875。显然与3.14是有误差的。

我们再通过另一种方法估算误差。从例子中可知,对于3.14的单精度浮点数,我们舍去了第24位以及之后,它们是:
0.00…(23个0)…00 [10001111…]×21b
为了方便计算,不妨假设此后全是0(即方括号中省略部分),也就是舍去了:
0.10001111b×2-23×21b
约为0.00000013317912817001;由于舍入进位关系,给第23位又加了1,所以加了:2-23×21,故而要减去这一部分。
所以,误差约为2-23×21 - 0.10001111b×2-23×21=0.00000010523945093155。所以结果大致为3.14+0.00000010523945093155=3.14000010523945093155
可见和上面计算结果大致相同。

4、机器ε(machine epsilon)

机器ε表示1与大于1的最小浮点数之差。不同精度定义的机器ε不同。以双精度为例,双精度表示的1是:

image-20210920100712599

而比1大的最小双精度浮点数是:

image-20210920100723698

可见,此二者之差为:2-52≈2.220446049250313e-16。所以它就是双精度浮点数的机器ε。
在舍入中,相对舍入误差不能大于机器ε的一半。比如上面的3.14的单精度浮点数,二者误差绝对值是0.0000001049041748046875…,从而相对舍入误差为0.0000001049041748046875…÷3.14≈0.00000003340897286773。而单精度浮点数的机器ε为2-23≈1.1920928955078125e-7,它的一半是0.00000005960464477539。显然,相对舍入误差小于单精度浮点数机器ε的一半。

Ⅲ - 非正规化:0的表示

从正规化中可知,无论如何浮点数都满足最左边是1。这就有一个严重问题:0没有办法被表示。为此,可以使用非正规化的表示方法,即让最左边默认为0,这样再另尾数也全部为0,就可以表示0了。

1、根据什么判断是非正规化还是正规化呢?

答案就是通过 指数部分 来反映。记得前面说过,双精度浮点数中,指数加移码 的范围可以从0到2047,然而0和2047是作为他用的。在这里,指数部分为0就代表着非正规化

所以,当见到指数部分为0是,尾数部分就不再是1.bbbbb...而是0.bbbbb...了。
再进一步,对于非正规化,可以看成是正规化中,小数点向左边跑了一位:1.bbbb…×2-1023=0.1bbbb…×2×2-1023==0.1bbbb…×2-1022(只是概念上理解,小数第一位也不一定非要是1,如0.001010×2^-1022也可)。所以,非正规化下表示为:

image-20210920101114078

现在,0就可以表示了。值得注意的是,此时0可以表示位+0和-0。

因为它的最左边不是1是0,实际上可以表示更小的数。双精度浮点数下,使用非正规化可以表示的最小的正数是0.00…01×2-1022也就是2-52×2-1022=2-1074

2、与机器ε的区别

请注意这个最小数和前面提到的机器ε的区别。比机器ε小的数是可以被表示出来的(利用非正规化)。但是当它们与其他浮点数做运算时,因为要转成同一种格式(正规化格式),从而可能会因为溢出位而被舍弃。最终结果就是,这些更小的数尽管能被表示,但是对运算结果没有影响。

Ⅳ - 浮点数加法

机器加法要先将两个操作数的小数点对齐,相加后再转为浮点数存储。这里最重要的一点是,尽管浮点数有位数限制,但是加法会在精度更高的寄存器中进行,这意味着,寄存器能够运算出比52位还要多的位数,但是在转回浮点数存储时,多余位数会被舍弃,造成两者相加的机器结果不严格等于算术结果。

image-20210920102646558

image-20210920102706603 并不表示在IEEE模型中可以忽略比image-20210920102736032 小的数只要它们在模型中可以表示出来.

假定没有把它们与单位大小的数相加减,那么用这样大的数进行的计算就如同精确的一样

Ⅴ - 无穷大与NaN

上面说到,在双精度浮点数中,指数为0表示非正规化,那么指数为2047(二进制是111 1111 1111b,即11位指数位全为1)就表示无穷大和 NaN(Not a Number)。具体表现在,当指数是2047,当尾数,全为0就表示无穷大,当尾数不全为0就表示 NaN。

Ⅵ - 舍入规则

1、以52位尾数位的双精度浮点数为例,舍入时需要重点参考第53位:

  • 若第53位为1,而第53位之后全部为0。此时就要使第52位为0:若第52位本来就是0则不管,若第52位为1,则第53位就要向第52位进一位,这样第52位就可以为0
  • 若不是上面的情况,即第53位1,但是第53位之后不全为0,则第53位就要向第52位进一完成上舍入。
  • 若也不是上面两种情况,那么第53位必为0,此时直接舍去不进位,称为下舍入。

由于存在这种舍入规则,浮点数一般在机器内都不会以原数精确相等的存储,这就会使在某些情况下,使用浮点数做算术运算时出现令人费解的情况,如在JavaScript中(数以双精度存储):

>>9.4-9-0.4===0 //9.4减去9再减去0.4,与0比较大小
<<false
>>(9.4-9-0.4).toFixed(20)
<<"0.00000000000000033307"

可见机器表示中,9.4-9-0.4不严格等于0,其结果有极小误差。因为按照上面的算法可知,9.4在机器内被表示为:9.4+0.2×2-49,而0.4被表示为0.4+0.1×2-52。这样,当9.4-9时(因为9是整数是可以精确存储的)得0.4+0.2×2-49,再减去0.4+0.1×2-52得3×2-53,约等于"0.00000000000000033307"。

2、循环小数的二进制转回十进制的技巧

某循环小数的二进制码是:0. 0110 0110 0110 0110 0110…b。可见是0110的循环,令x为其十进制数:x=0.01100110…b,则24x=110,01100110…b,两式相减得:(24-1)x=110b,即15x=6,从而x=6/15=0.4

五、回归JS

众所周知JS仅有Number这个数值类型,而Number采用的时IEEE 754 64位双精度浮点数编码。而浮点数表示方式具有以下特点:

  1. 浮点数可表示的值范围比同等位数的整数表示方式的值范围要大得多;

  2. 浮点数无法精确表示其值范围内的所有数值,而有符号和无符号整数则是精确表示其值范围内的每个数值;

  3. 浮点数只能精确表示m*2e的数值;

  4. 当biased-exponent为2e-1-1时,浮点数能精确表示该范围内的各整数值;

  5. 当biased-exponent不为2e-1-1时,浮点数不能精确表示该范围内的各整数值。

由于部分数值无法精确表示(存储),于是在运算统计后偏差会愈见明显。

Ⅰ - 为何 0.1 + 0.2 === 0.30000000000000004 ?

1、文字描述

在浮点数运算中产生误差值的示例中,最出名应该是0.1 + 0.2 === 0.30000000000000004了,到底有多有名?看看这个网站就知道了http://0.30000000000000004.com/。也就是说不仅是JavaScript会产生这种问题,只要是采用IEEE 754 Floating-point的浮点数编码方式来表示浮点数时,则会产生这类问题。下面我们来分析整个运算过程。

  1. 0.1 的二进制表示为 1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-4;

  2. 当64bit的存储空间无法存储完整的无限循环小数,而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.1实际存储时的位模式是0-01111111011-1001100110011001100110011001100110011001100110011010;

  3. 0.2 的二进制表示为 1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-3;

  4. 当64bit的存储空间无法存储完整的无限循环小数,而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.2实际存储时的位模式是0-01111111100-1001100110011001100110011001100110011001100110011010;

  5. 实际存储的位模式作为操作数进行浮点数加法,得到 0-01111111101-0011001100110011001100110011001100110011001100110100。转换为十进制即为0.30000000000000004。

2、图文解析

image-20210920100244664

image-20210920104410607

Ⅱ - 为何 0.7 * 180===125.99999999998 ?

  1. 0.7实际存储时的位模式是0-01111111110-0110011001100110011001100110011001100110011001100110;

  2. 180实际存储时的位模式是0-10000000110-0110100000000000000000000000000000000000000000000000;

  3. 实际存储的位模式作为操作数进行浮点数乘法,得到0-10000000101-1111011111111111111111111111111111111111101010000001。转换为十进制即为125.99999999998。

Ⅲ - 为何 1000000000000000128=== 1000000000000000129 ?

1. 1000000000000000128实际存储时的位模式是0-10000111010-1011110000010110110101100111010011101100100000000001;

2. 1000000000000000129实际存储时的位模式是0-10000111010-1011110000010110110101100111010011101100100000000001;

3. 因此1000000000000000128和``1000000000000000129的实际存储的位模式是一样的。

Ⅳ - 为何 0.1+0.1 === 0.2 ?

实际上 0.1+0.1 得到的是 0.20000000…00x,也是有误差,但是JS只取17位有效数字,四舍五入后就相当于是正确答案了

六、JavaScript中使用 !!

1、了解布尔值为 false的值

首先你要知道在JavaScript中,布尔值为 false的值有如下几个:

  • undefined
  • null
  • false
  • 0
  • NaN
  • ""''(空字符串)

这样才能知道更好的理解这个用法

2、!!的作用

!!的作用是将值的类型强制转化为布尔类型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imxzdxmW-1669819360882)(https://xingqiu-tuchuang-1256524210.cos.ap-shanghai.myqcloud.com/2767/js%E4%B8%AD%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81!!%EF%BC%9F%E4%B8%AD%E7%9A%84%E5%9B%BE%E7%89%871.png)]

可以发现 undefined==falsenull==falseNaN==false的值均为 false,如果加上 !!,就都相等了

3、使用场景举例

Ⅰ-if语句中使用

首先,我们要知道if语句的条件表达式会执行隐式类型转换

var let = 0;
if(b) console.log('true');
else console.log('false'); // 执行这行代码

if(b&&true) console.log('true');
else console.log('false'); // 执行这行代码

if(!!b&&true) console.log('true');
else console.log('false'); // 执行这行代码

所以发现并无任何影响,也证明了if其实已经默认转换了,我们是否增加!!并没有任何影响

这也是当初我当初觉得没用的原因了

Ⅱ-返回值强制转换

const test=value=> value && true;
console.log(test(0)); // 0

const test=value=> !!value && true;
console.log(test(0)); // false

可以看到,!!value强制转化为布尔值。

else console.log(‘false’); // 执行这行代码

if(!!b&&true) console.log(‘true’);
else console.log(‘false’); // 执行这行代码


所以发现并无任何影响,也证明了if其实已经默认转换了,我们是否增加`!!`并没有任何影响

这也是当初我当初觉得没用的原因了

Ⅱ-返回值强制转换

const test=value=> value && true;
console.log(test(0)); // 0

const test=value=> !!value && true;
console.log(test(0)); // false

可以看到,!!value强制转化为布尔值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值