1.位运算
~符
~是按位取反的运算符,得到的结果是它的相反数减一(和补数取模有关,感兴趣可以了解下)。一般在可能返回-1的函数中比较好用。-1的相反数刚好是0,转成布尔就是false。所以:
if(arr.indexOf(a)!==-1){...}
//等价于
if(~arr.indexOf(a)){...}
~~符
~~可以用来取整,因为位运算会舍弃浮点数:
~~1.23 === 1 //true
~~-1.23 === -1 //true
当然其它的位运算也可行,只要经过运算能复原原来的数,例如:
//y|0 === int(y)
(1.23|0) === 1 //true
(-1.23|0) === -1 //true
//x^y^x = int(y);x可以换成任何整数,这种写法太花哨,leader可能会打回来,哈哈
(111^1.23^111) === 1 //true
(111^-1.23^111) === -1 //true
有了快速取整就有了判断一个数字是不是整数的方法:
function isInteger(num){return num === ~~num;}//判断是不是整数
但是对于比较大的浮点数,这种取整方式也会有问题:
因为取整的原理是进行位运算会强制转换数据类型,该整数不能大于2**31-1(切记切记)。所以一般不要用在可能出现大数的环境中,不然bug找得你头秃。
&符
&是按位与,常用来判断奇偶。我们平时用取模判断奇偶可能还会遇到小数的情况,导致判断出问题:
12.3%2 //0.3000000000000007
这个时候判断等于1或者0都会有问题,而使用&则比较方便:
12.3&1 === 0//偶数最后一位为0,二进制表示1100
13.3&1 === 1//奇数数最后一位为0,二进制表示1101
lowbit运算
在算法中我们树状数组进行查询和更新,通常会访问lowbit节点。
表示最低位1和后面0组成的数字,比如说6的二进制是110那么lowbit就是最后的10组成的数也就是2。
x&-x
移位
<<和>>是算术移位,将不会改变符号位。
>>>是逻辑右移,符号位也会跟着移动。JavaScript没有实现逻辑左移。
5>>1//2 因为5的二进制是101 右移一位后变成了10二进制就是2,相当于除以二向下取整
2<<1//4 2的二进制是10 左移一位后就就变成了100二进制就是4,相当于乘以2
-1>>>1//2147483647 -1的补码表示为0XFFFFFFFF全为1,逻辑移位后符号位就是0也就是正数,且全1,也就是32位有符号能表示的最大正数了
同样位移运算对于超出32位整数的值会出现问题:
那么创建一个稳定的系统一定要在合适的场景使用位运算,如果滥用将会出奇怪的bug。
2.实用函数
function UUID (){//生成UUID,唯一id,几乎无碰撞。ps:用来当dom便历的key可是很合适啊
var temp_url = URL.createObjectURL(new Blob());
var uuid = temp_url.toString(); // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3
URL.revokeObjectURL(temp_url);
return uuid.substr(uuid.lastIndexOf("/") + 1);
}
function timeFormate(timeStr,formStr){//时间序列化,非常方便格式转化
if(!timeStr){return ""}
let time=new Date(timeStr),str=formStr;
if(time!=="Invalid Date"){
str=str.replace("yyyy",time.getFullYear());
str=str.replace("MM",(time.getMonth()+1).toString().padStart(2,"0"));
str=str.replace("dd",time.getDate().toString().padStart(2,"0"));
str=str.replace("hh",time.getHours().toString().padStart(2,"0"));
str=str.replace("mm",time.getMinutes().toString().padStart(2,"0"));
str=str.replace("ss",time.getSeconds().toString().padStart(2,"0"));
}
return str;
};
console.log(timeFormate(new Date(),"yyyy-MM-dd hh:mm:ss"));
//2021-11-24 21:26:56
console.log(timeFormate(new Date(),"yyyy-MM-dd"));
//2021-11-24
console.log(timeFormate(new Date(),"yyyy年MM月dd日hh时mm分ss秒"));
//2021年11月24日21时26分56秒
console.log(timeFormate(new Date(),"yyyy/MM/dd hh:mm:ss"));
//2021/11/24 21:26:56
function openFile(accept){//打开文件选择,不需要额外dom布局
return new Promise((rs)=>{
let fileInput = document.createElement("input");
fileInput.setAttribute("display","none");
fileInput.setAttribute("type","file");
fileInput.setAttribute("accept",accept);
fileInput.addEventListener("change",(e)=>{
rs(fileInput,e);//e中不包含target,所以需要直接用创建的元素获取传入的文件
});
fileInput.click();//无交互时自动点击无效.
});
}
3.冷知识
(1)我们对es6的Set还是停留在去重上,但是它还有个重要的作用就是保存引用。Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。这就相当于能存储指针,对我们操作链表和树有很大的帮助。比如说我们要遍历存储链表每个节点的引用:
//head是一条链表的头部
let arr = [];
for(;;){
if(head===null){break;}
let node = head;
arr.push(node);
head = head.next;
}
本意是想存储链表每个节点的引用,但是没法达到效果,因为node并不会新建一个引用还是指向head,head一变所有的数组存的node都会变,根本达不到效果。而set就能实现我们想要的效果:
let set = new Set;
for(;;){
if(head===null){break;}
set.add(node);
head = head.next;
}