实用的js运算符
一、可选链运算符?.
- 可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
- ?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。
- 与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。
let obj = {
user:{
one:{
firstName: 'Tom';
},
},
};
// 若存在obj.user.one.firstName该属性,那要安全的读取它就需要这样写:
let firstName =(obj
&& obj.user
&& obj.user.one
&& obj.user.one.firstName)|| 'default'
// 用可选链写做:
let firstName = obj?.user?.one?.firstName || 'default';
通过使用 ?. 操作符取代 . 操作符,JavaScript 会在尝试问 obj.user.one.firstName 之前,先隐式地检查并确定 obj.user 及obj.user.one既不是 null 也不是 undefined。如果是 null 或者 undefined,表达式将会短路计算直接返回 undefined。
语法:
- obj?.prop
- obj?.[expr]
- arr?.[index]
- func?.(args)
下面是?.运算符常见形式,以及不使用可选链时的等价形式。
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
要注意的是上面代码的后两种与函数的使用,如果a?.b()和a?.()。如果a?.b()里面的a.b有值,但不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是null或undefined,但也不是函数,那么a?.()会报错。
二 、空值合并运算符 ??
- 如果第一个参数不是 null/undefined,将返回第一个参数,否则返回第二个参数。
- 注意,虽然 JS 中的未定义 undefined、空对象 null、数值 0、空数字 NaN、布尔 false,空字符串’ ’ 都是假值,但??非空运算符只对 null/undefined 做处理。
console.log( 1 ?? 'superJS' ); // 1
console.log( false ?? 'superJS' ); // false
console.log( null ?? 'superJS' ); // superJS
console.log( undefined ?? 'superJS' ); // superJS
三、逻辑运算符
1.逻辑运算符与 && 和或 ||
- 逻辑与(&&):看左边的值是真还是假,如果是真,返回的是右边的值,如果是假返回的是左边的值(只有false 、0、NaN、null、undefined、空字符串为假, 其余都是真)。
例:如果某个值为 true,则运行某个 function
// 第一个操作数求值为 true 就直接返回第一个数 **(短路操作)**
1alert(null && 'superJS') // null
2alert(NaN && 'superJS') // NaN
3alert(0 && 'superJS') // 0
4alert(false && 'superJS') // false
5alert(undefined && 'superJS') // undefined
6alert('' && 'superJS') // '';
function say() {
console.log("superJS");
}
let type = true;
type && say(); // superJS
- 逻辑或(||): 看左边的值是真还是假,如果是真,返回的是左边的值,如果是假返回的是右边的值(只有 false 、0、NaN、null、undefined、空字符串为假, 其余都是真)。
例:给某个变量设置初始值
let student = {
name: "shanguagua",
};
console.log(student.age || "superJS"); // superJS
注:(在js中&&运算符优先级大于||)
2.逻辑运算符非 !
非运算符!可将变量转换成 boolean 类型,null、undefined 和空字符串’'取反都为 true,其余都为 false。一般来说会有好几种用法,!,!!,!=,!==。
-
利用!取反
-
利用!!做类型判断
判断变量 a 不等于 null,undefined 和’'才能执行的方法。
var a;
if (a != null && typeof a != undefined && a != "") {
//balabala
}
// 等价于
if (!!a) {
//balabala
}
四、位运算符
运算符 | 名称 | 描述 |
---|---|---|
& | 与 | 如果两位都是1 则设置每位为1 |
| | 或 | 如果两位之一为1 则设置每位为1 |
^ | 异或 | 如果两位只有一位为1 则设置每位为1 |
~ | 非 | 按位取反所有位 |
<< | 零填充左位移 | 通过从右推入零向左位移,并使最左边的位脱落。 |
>> | 有符号右位移 | 通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落。 |
>>> | 零填充右位移 | 通过从左推入零来向右位移,并使最右边的位脱落。 |
*位运算是低级的运算操作,所以速度往往也是最快的(相对其它运算如加减乘除来说),并且借助位运算的特性还能实现一些算法。恰当地使用运算有很多好处。下面举几个例子。
1.按位非~
先来个小例子:
~1 = -2
运算过程:
在 JavaScript 中,所有整数字变量默认都是有符号整数,一个整数是4个字节,32位,最高位为符号位,0代表正数,1代表负数。
1的二进制表示为:00000000 00000000 00000000 00000001
按位非(按位取反)后是:11111111 11111111 11111111 11111110
由于计算机中的数字都是以补码的形式存在以及运算的,这个按位非后的数由于最高位符号位为1,是负数,因此再结合上述机器存储的 编码方式 得:
反码 10000000 00000000 00000000 00000001
补码 10000000 00000000 00000000 00000010
根据机器存储一个具体数字的 编码方式 知:
对于正数:原码 = 反码 = 补码。
对于负数:反码为原码的最高位不变,其余位取反。补码为在反码的基础上加1。
由此可知1按位取反的结果就是-2。
下面是~的两个小巧用:
1) 使用~~ 操作符来替代正数的 Math.floor( ),替代负数的 Math.ceil( )
Math.floor()的作用是丢弃小数部分,获得整数。
因为只有整型可以进行位运算,小数是没有位运算的,所以对小数进行位运算会直接把小数给舍去。因 此~1.111111取反后的结果就是-2,再把-2取反,还是回到1,因此用这种方式实现了比Math.floor更快的运算。
Math.floor(1.111111) === 1; // true
~~1.111111 === 1; // true
Math.ceil(-6.6) === -6; // true
~~-6.6 === -6; // true
2)使用按位非~判断引索存在
也是一个非常实用的小技巧,如判断一个字符是否存在
// 如果url含有?号,则后面拼上&符号,否则加上?号
url += ~url.indexOf("?") ? "&" : "?";
// 不用~的写法
url += url.indexOf("?") === -1 ? "&" : "?";
这是因为:
~-1 === 0
-1原码 10000000 00000000 00000000 00000001
-1反码 11111111 11111111 11111111 11111110
-1补码 11111111 11111111 11111111 11111111
~-1 00000000 00000000 00000000 00000000
2.异或^
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
异或的运算规律顾名思义就是0和1异或的时候相同的异或结果为0,不同的异或结果为1。
1)使用异或交换两个数
let a = 5,
b = 6;
// 使用零食变量c交换a, b的值
let c = a;
a = b;
b = c;
// 使用异或交换a,b的值
a = a ^ b;
b = a ^ b; // b 等于 5
a = a ^ b; // a 等于 6
这是因为:
把1式带入2式可得:b = (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a;
而当计算3式时,b已经变为a了,即: a = a ^ (a ^ b) = 0 ^ b = b;
这也是两个变量交换值的最快方法,不需要任何额外的空间。
2) 找出只出现了一次的元素
设计一个算法要求不占用额外的空间。
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
/**
* @param {number[]} nums
* @return {number}
*/
var singleNumber = function(nums) {
let result = 0;
for(let i=0; i< nums.length; i++){
result = nums[i] ^ result;
}
return result
};
- 使用异或运算,将所有值进行异或
- 不同为真,相同为假,所以
a^a = 0 ;0^a = a
- 因为异或运算 满足交换律
a^b^a = a^a^b = b
所以数组经过异或运算,单独的值就剩下了
3)加密
异或还经常被用于加密,比如在前面分享过的DES加密中就多次用到了异或的位运算。
第一步,明文(text)与密钥(key)进行异或运算,可以得到密文(cipherText)。
text ^ key = cipherText
第二步,密文与密钥再次进行异或运算,就可以还原成明文。
cipherText ^ key = text
原理很简单,如果明文是 x,密钥是 y,那么 x 连续与 y 进行两次异或运算,得到自身。
(x ^ y) ^ y
= x ^ (y ^ y)
= x ^ 0
= x
4)数据备份
异或运算可以用于数据备份。文件 x 和文件 y 进行异或运算,产生一个备份文件 z。
x ^ y = z
以后,无论是文件 x 或文件 y 损坏,只要不是两个原始文件同时损坏,就能根据另一个文件和备份文件,进行还原。
x ^ z
= x ^ (x ^ y)
= (x ^ x) ^ y
= 0 ^ y
= y
上面的例子是 y 损坏,x 和 z 进行异或运算,就能得到 y。
3.按位与&和按位或|
&: 0与 0跟1都为0,全1才为1;
|:1或 0跟1都为1,全0才为0。
使用位运算符时会抛弃小数位,我们可以利用|0来给数字取整。也可以使用&1来判断奇偶数。
- 通过|运算符取整
10.6 | 0 // 10
1010
0000
——————
1010 -> 10
- 判断奇偶数
// 跟1进行与运算,偶数结果为0,奇数结果为1
let num = 5;
!!(num & 1); // true
!!(num % 2); // true
// 0101
& 0001
——————
0001
4.左移<< 、右移>>
- 左移<<:表示将一个数的二进制值向左移动指定的位数,符号位始终保持不变。如果右侧空出位置,则自动填充为 0。超出 32 位的值,则自动丢弃。
console.log(5 << 2); //返回值20
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8D5U6cxb-1662693923271)(Image.png)]
如果要求2的n次方:
function power(n) {
return 1 << n;
}
power(5); // 32
- 右移>>:表示将一个数的二进制值向右移动指定的位数,它把 32 位数字中的所有有效位整体右移,再使用符号位的值填充空位,即正数补0,负数补1。移动过程中超出的值将被丢弃。
console.log(1000 >> 8); //返回值3
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UvbyuRsW-1662693923273)(Image%20%5B1%5D.png)]
求一个数的二分之一:
var num = 64 >> 1; // 32
有符号左移与右移不会影响符号位。
- 零填充(无符号)右移>>>:正数的无符号右移与有符号右移结果是一样的。负数的无符号右移会把符号位也一起移动,而且无符号右移会把负数的二进制码当成正数的二进制码。
console.log(-1000 >> 8); //返回值 -4
console.log(-1000 >>> 8); //返回值 16777212
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BPDeoli5-1662693923274)(Image%20%5B2%5D.png)]
所以可以利用无符号右移来判断一个数的正负:
function isPos(n) {
return (n === (n >>> 0)) ? true : false;
}
isPos(-1); // false
isPos(1); // true
注:js及其他语言中都没有无符号左移运算。