重学JS---迷人的运算符

244 阅读11分钟

这篇文章 偏基础,一篇查漏补缺的文章。来掘金试试水

闲话少说,开干!!

注意 此篇文章 在浏览器控制台运行(吐槽: 火狐的控制台用的好不得劲!)


基础

加减乘除等基础运算

这里有一些基础的运算符。

+(加)、-(减)、*(乘)、/(除)、 %(取模)、++(自增)、--(自减)、**(幂)。

1+2 //3
2 - 1 //1
1 * 3 //3
4 / 2 //2
5 % 2 //1

// 另外
0 / 0 // NaN
1 / 0 // Infinity
1 / -0 // -Infinity
-1 / 0 // -Infinity
-1 / -0 // Infinity

// 自增和自减需要对变量使用
let a = 1;
a++ //1
a //2
++a //3
a //3
let b = 1;
b-- //1
b //0
--b //-1
b //-1

//幂运算
2**3 //8;

读到这,你可能会想,哇,你这真的水,这么基础都要拿出来说。别急,看完这篇文章你会更坚定你的想法的(哈哈,开玩笑的,慢慢由浅入深,这个意思你懂吧🤓)

  • 关于 Math

Math有一些常量,也有一些负责运算的API,比如 . 常量 比如 Math.LN2(2的自然对数)、 Math.LN10 (10的自然对数)、Math.PI(大名鼎鼎的π)

运算API比如 Math.abs(x)(绝对值),Math.ceil(x)(向上取整),Math.floor(x)(向下取整),Math.pow(x, y)(幂) , Math.sqrt(x)(平方根) , Math.max([x[, y[, …]]])(最大值),Math.min([x[, y[, …]]])(最小值)等,还有一些 正余弦,正余切,对数运算因为不常用就不举例了。

Math.abs(-1) //1
Math.ceil(1.2) // 2 
Math.floor(1.2) // 1 
Math.pow(2, 3) // 8
Math.sqrt(4) // 2 
Math.max(4,2) // 4 
Math.max(-4,2) // 2

其实上面大部分操作都可以用操作符实现

另外 + 遇到字符串会有拼接的作用,

'hello '+ 'word'; // hello word

也可做一元运算符,有一个类型转换的作用,减法也如此

// 相当于 parseInt 与 parseFloat
+'1' - +'2' // 相当于1-2 = -1

前几天同事推荐了一套题,挺有趣的,可以猛戳 这里⇲ 去自测一波, 中间有这样一道题(还有许多类似的题):

image.png你选啥?

分析 结局总结了我在本测验中涵盖的大部分奇怪的语法。让我们一块一块地分解它:

-""; // -> -0
+"1"; // -> 1
Number(null); // -> 0 
Number([,]); // -> 0 
//把它们加在一起:
-0 + 1 * 0 - 0; // -> 0

中间也涉及到类型转换的一些技巧。

关于类型转换,推荐

语法糖(=、+=、-=、*=、/=、%=)

这里还有一些 结合赋值运算符 = 组合的语法糖 = 是作为很多程序(基本所有)的赋值运算符,把右边的值赋值给左边,不做赘述。

// => 为等价符号
a += b => a = a + b
a -= b => a = a - b
a *= b => a = a * b
a /= b => a = a / b
a %= b => a = a % b

比较运算符与关系运算符

💎比较就是判断相等

主要包含 =====!=!==

分别是宽松相等和严格相等,宽松不相等和严格不相等

严格判断比宽松多了类型判断

'6' == 6 //true
1 == true //true
undefined == null //true

'6' === 6 //false
1 === true //false
undefined === null //false

💎关系就是比较大小

主要包含 ><>=<=, 很简单。

1 > 2 //false
1 < 2 //true
1 >= 1 //true
1 <= 1 //true

是不是感觉智商受到了侮辱🤡?哈哈哈哈~~

逻辑运算符

主要包含 &&(与,有假取假)、||(或,有真取真,全假取尾)、!(非,取反,布尔值) 可以当做判断来用,也可以取值做个兜底

// ||会把 '' 和 0 当成假值
false || 1 //1
false || 0 //0
9 || false //9

1 && 0 //0
0 && 1 //0
false && '1' //false
'2' && '3' //"3"

!0 //true
!null //true
!undefined //true
!true //false
!-1 //false

三目运算符(a? b:c)

这个简单 ,不过判断规则也是把空字符串 ''0 当成假值

a = a ? a : b;

逗号运算符(,)

逗号操作符 对它的每个操作数求值(从左到右),并返回最后一个操作数的值

var a = 1,b = 2;
a = (1,2,4,9);
a //9
a = (3,b = 5, 8);
a //8
b //5
a = (2, b=6);
a //6
b //6

其实很常见的,大家定义变量也用到过的

let a = 1, b = 2, c = 3;

void 运算符

这个我接触的不多,不过也有一些用处的。

void 运算符 对给定的表达式进行求值,然后返回 undefined。

感觉 void 用的比较少了,下面写的功能是去网上网罗的一些。有点 为赋新词强说愁 的感觉

  • 定义空连接
<a href="javascript:void(0);">
  这个链接点击之后不会做任何事情,如果去掉 void(),
  点击之后整个页面会被替换成一个字符 0。
</a>
  • 立即执行函数

这个功能,嗯,,,,怎么说呢,哎~~~

function iife() { console.log('foo') }()       // 报错,因为JS引擎把IIFE识别为了函数声明
void function iife() { console.log('foo') }()  // 正常调用
~function iife() { console.log('foo') }()      // 也可以使用一个位操作符
(function iife() { console.log('foo') })()     // 或者干脆用括号括起来表示为整体的表达式
  • 箭头函数中避免泄露

注意时避免 doSomething 返回值 泄露出来给箭头函数调用者,感觉有点鸡肋

button.onclick = () => void doSomething();

进阶

数值分隔符(_)

ES2021 引入了数值分割符 _,在数值组之间提供分隔,提升了数值阅读体验

var num =  30_0000_0000 // 30亿
num //3000000000

位运算符

这其实我写这篇文章的初衷,前面写的都当做复习吧。

注意

  • 位运算符只能用于整型
  • 不适合用于大数值

位运算符涉及二进制运算,语法简洁,能力很大。

复习一下二进制运算?进制转换参考 Number.prototype.toString()⇲

二进制运算

二进制数就是 0, 1 机器码组成的数。咋们平常的看到数是十进制的. 比如 143 ,从低到高位分别是 3,4,1,则143等于

3乘以10的零次方 + 4乘以10的一次方 + 1乘以10的二次方

算式为

3 * 10**0 + 4 * 10**1 + 1 * 10**2 // 143

二进制 1011 就等于

1 * 2**0 + 1 * 2**1 + 0 * 2**2 + 1 * 2**3 // 表示十进制数 11

有点过于基础了,哈哈哈

(15).toString(2) //得到二级制 "1111"

二进制运算有 很多运算 参考 - MDN

💎&,按位与(全1为1) 在a,b的位表示中,每一个对应的位都为1则返回1, 否则返回0.

15 & 9 // 9
// 1111 & 1001 => 1001

💎|,按位或(有1为1) 在a,b的位表示中,每一个对应的位,只要有一个为1则返回1, 否则返回0.

15 | 9 // 15
// 1111 | 1001 = 1111

💎 ^,按位异或(有异为1) 在a,b的位表示中,每一个对应的位,两个不相同则返回1,相同则返回0.

15 ^ 9 // 	1111 ^ 1001 = 0110

💎~,按位非 反转被操作数的位。

~15 //~00000000...00001111 = 11111111...11110000 = -16

注意位运算符“非”将所有的32位取反,而值的最高位(最左边的一位)为1则表示负数(2-补码表示法)

💎<<,左移 将a的二进制串向左移动b位,右边移入0.

15 << 1 // 30,相当于乘以2
// 1111 << 1 = 11110

💎>>,算术右移 把a的二进制表示向右移动b位,丢弃被移出的所有位.

15 >> 1 // 7 相当于除以2再向下取整。
// 1111 >> 1 = 111

💎 >>>,无符号右移 把a的二进制表示向右移动b位,丢弃被移出的所有位,并把左边空出的位都填充为0, 对非负数值,补零右移和带符号右移产生相同结果.

负数用正数的补码表示,原码取反再去补码(+1),比如 1 变为 -1 0000 0001 => 1111 1110 => 1111 1111 注意 11111111 如果当做有符号数(最高位,0正1负)就是 -1 ,无符号数就是 255

15 >>> 1 // 7 相当于除以2再向下取整。
// 1111 >>> 1 = 111

位运算符的使用场景

  • 判断奇偶
// 做 & 1运算,结果为1为奇数, 0为偶数
5 & 1 // 1 
4 & 1 // 0
  • 向下取整
~~ 4.1 //4
~~ 4.7 //4
~~-4.5 //-4

4.5 >> 0 //4
4.8 >> 0 //4
-4.1 >> 0 //-4
-4.1 << 0 //-4
4.8 << 0 //4
// >>> 不可对负数取整
4.8 >>> 0 //4
-4.8 >>> 0 //4294967292
  • Number 类型的值交换
// 等同于 es6的 [a,b] = [b,a]
let a = 2, b=3;
a ^= b //1 此时a:1  b:3
b ^= a //2 此时a:1  b:2
a ^= b//3 此时a:3 b:2
  • rgb与16进制色值转换
/**
 * 16进制颜色值转RGB
 * @param  {String} hex 16进制颜色字符串
 * @return {String}     RGB颜色字符串
 */
  function hexToRGB(hex) {
    var hexx = hex.replace('#', '0x')
    var r = hexx >> 16
    var g = hexx >> 8 & 0xff
    var b = hexx & 0xff
    return `rgb(${r}, ${g}, ${b})`
}

/**
 * RGB颜色转16进制颜色
 * @param  {String} rgb RGB进制颜色字符串
 * @return {String}     16进制颜色字符串
 */
function RGBToHex(rgb) {
    var rgbArr = rgb.split(/[^\d]+/)
    var color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3]
    return '#'+ color.toString(16)
}
hexToRGB('#ff00ff')   // 'rgb(255,0,255)'
RGBToHex('rgb(0,255,255)')  // '#00ffff'
  • 快排中的取二分数
9 >> 1 //4
6 >> 1 //3
  • 判断值是否相等
// 相等 结果为 0 ,否者为 非0
10 ^ 9 //3
9 ^ 9 //0
  • 检测是否找到数组项

里用取反()运算符对 -1 以外的任何值,都返回 truthy 值。对它进行非运算,直接 !〜

// 其实includes比这方便。
!!(~arr.indexof(item)) // 找到为true

!!~0 //true
!~-1 //true
!!~1 //true

另外需要注意:

补充

  • 整数精度(不使用小数点或指数计数法)最多为15位。小数精度的最大位数是17,但是浮点运算并不总是100% 准确。
  • 位运算直接对二进制位进行计算,位运算直接处理每一个比特位,是非常底层的运算,好处是速度极快,缺点是很不直观,许多场合不能够使用。
  • 位运算只对整数起作用,如果一个运算数不是整数,会自动转为整数后再运行。
  • 在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数

总结: 位运算符 带来的速度肉眼不可见,但理解成本会增加,尽量少用.

空合并运算符(??

这是为了改善 || 运算符的一些问题(),前面提过 ||会把 ''0 当成假值 详情看代码,falsy 的就只剩下 nullundefinedfalse

// 如果全为真值就应该取第一个
1 || 2 //1

0 || 2 //2
'' || 2 //2

0 ?? 2 //0
'' ?? 2 //""

可选链接运算符 (?.)

没有它可能需要这样写:

obj && obj.prop && obj.prop.a

现在只需要

obj??.prop??.a

香的很。和你做个比喻吧,比蚊香还香😁

扩展

这里还有一些起奇妙的运算符,浏览器慢慢把标准落实,这也是很香的东西。

逻辑空分配(??=

理所当然,可能 会以为 x ??= y 是下面的简写

x = x ?? y

表面的意思就是 x 为 falsy(null, undefined, false) 时 y 就赋值给 x? 代码检测一下

var a = false ?? '赋值成功!' // a 为 false
var a = true ?? '赋值成功!' //a 为 true

这是为啥,因为 合并运算符 ?? 是从左到右操作的, 也就是说 当 左边的赋值操作 (a = false/true)成功后 右边是不会执行的,赋值怎么可能失败呢,对吧

x ??= y 所以相当于

x ?? (x = y)

注意,仅仅是相当于,当然存在一些特殊情况!!!

  • 左边值(x)仅当为 nulllish(null, undefined) 时才会执行!!
function test(val){ var a= val;  a ??= '赋值成功!'; return a  }
test() //"赋值成功!"
test(1) //1
test(null) //"赋值成功!"
test(false) //false

估计设计它的目的就是为那些 未赋值的变量赋值null 也被当做未赋值)

逻辑或分配(|| =

理解了上面的 ??=,这就好理解了嘛, a ||= b全等于

a || (a = b)

除了代码演示,我无fa可说🙃

function test(val){ var a= val;  a ||= '赋值成功!'; return a  }
test() //"赋值成功!"
test(0) //"赋值成功!"
test(false) //"赋值成功!"
test(null) // "赋值成功!"
test(undefined) //"赋值成功!"
test('') //"赋值成功!"

场景: 如果a存在默认值,就保存它,不存在就赋新值。

逻辑与分配(&& =

理解了上面的 ??=,这也好理解了嘛, a &&= b全等于

a || (a = b)

除了代码演示,我无fa可说🙃

function test(val){ var a= val;  a &&= '赋值成功!'; return a  }
test(1) // "赋值成功!"
test(0) // 0
test(false) // false
test() // undefined
test(null) // null
test('') // ""
test(NaN) // NaN

场景: 对已经存在的值进行更新

运算符优先级

Operator typeIndividual operators(依次递减)
member. []
call / create instance() new
negation/increment! ~ - + ++ -- typeof void delete
multiply/divide* / %
addition/subtraction+ -
bitwise shift<< >> >>>
relational< <= > >= in instanceof
equality== != === !==
bitwise-and&
bitwise-xor
bitwise-or
logical-and&&
logical-or
conditional?:
assignment= += -= *= /= %= <<= >>= >>>= &= ^==
comma,

参考

站在别人肩膀上能看的更远,谢谢💖💖💖以下文章作者~~~