最近在阅读Koa2源码,在阅读过程当中get到了一些特别的技巧:就是关于~这个家伙的。为此作了一些调研,把学到的东西分享给你们~~javascript
非科班出身的同窗可能会对这个~符号比较懵,这里简单介绍一下(科班同窗就能够跳过啦)。~是位运算符的一种,它的做用是按位取反。java
咱们都知道,计算机其实只认两个符号:0、1,但其实在计算机中,数值一概是用补码来表示和存储,所以位运算也是基于补码运算的。git
这里又涉及到了补码的概念,因此简单介绍一下原码、补码:github
JS中的~与其余语言中的~略有不一样,这里先简单介绍一下比较常规的~。数组
在对正数进行~操做时:函数
举个实例:性能
对负数进行~操做时:测试
举个实例:ui
总结:~x = -x - 1,如 ~3 = -3-1 = -4 ;~(-3) = 3 - 1 = 2es5
而在JS里,~会首先对运算对象进行转换为整数的操做,转换规则参考ECMA-262规范的ToInt32。这里简单翻译一下,假设咱们输入的参数为input;
举个例子:~undefined,在转换过程当中,ToNumber(undefined)结果为NaN,ToInt32(NaN)结果为+0,所以~undefined结果与~+0一致,为-1。另外对于对象的转换,核心规则主要根据[[DefaultValue]] (hint)。下面各举一些对象转换的例子:
> ~{}
-1
> ~[]
-1
> ~NaN
-1
> ~[1]
-2
> ~[2,3]
-1
> ~{toString: () => '45'}
-46
> ~{toString: () => '45',valueOf: () => 123}
-124
复制代码
const target = [1,4,56,7]
console.log(!!~target.indexOf(8)) //false
// 经过~转换为0后,不少处理能够简化,
// 能够免去写 !== -1 或者 === -1等烦恼
复制代码
const target = '321'
console.log(typeof ~~target) // number
复制代码
const target1 = '321.235'
console.log(~~target1) // 321
const target2 = '-321.77'
console.log(~~target2) // -321
复制代码
> ~~undefined == !!undefined
true
> ~~null == !!null
true
> ~~0 == !!0
true
> ~~1 == !!1
true
// 注意,并非全部同样的输入,~、!计算结果都相等
> ~~'a' == !!'a'
false
> ~~[0] == !![0]
false
> ~~-1 == !!-1
false
> ~~2 == !!2
false
复制代码
上面提到了~的几种用处,归根结底都是利用了~能快速转换整数的能力,下面分别对~~与其它经常使用的转换操做进行简单的速度比较,测试环境:macOS Mojave 10.14.2 Node v8.12.0
let {numArr,stringArr} = require('./data');
let func1 = number => {
let start = performance.now();
let b = ~~number; // 测试符号:包括 ~ 、Math.floor、parseInt、+ 四种
return {
value: b,
time: performance.now() - start
};
};
let totalTime = 0;
numArr.map(item => {
totalTime += func1(item).time
})
console.log({
type: 'fun1',
totalTime,
len: numArr.length,
average: totalTime / numArr.length
})
复制代码
因为篇幅问题,这里不放全全部代码了,主要修改内容为有测试的运算符号以及数据来源。数据为MockJS生成随机浮点数数组,包含正负浮点数、正负浮点字符串、随机Null、undefined、NaN数组;
操做符号 | 测试数量 | 总耗时 | 平均时间 |
---|---|---|---|
~~ | 609 | 0.24905507266521454 | 0.00040895742637966264 |
Math.floor | 609 | 0.34583795070648193 | 0.00056787840838502780 |
parseInt | 609 | 0.28167189657688140 | 0.00046251542951868867 |
+ | 609 | 0.31073787808418274 | 0.00051024282115629350 |
操做符号 | 测试数量 | 总耗时 | 平均时间 |
---|---|---|---|
~~ | 701 | 0.4971509501338005 | 0.0007092024966245371 |
Math.floor | 701 | 0.5587870776653290 | 0.0007971284988093138 |
parseInt | 701 | 0.5465480908751488 | 0.0007796691738589855 |
+ | 701 | 0.4985470473766327 | 0.00071119407614355590 |
操做符号 | 测试数量 | 总耗时 | 平均时间 |
---|---|---|---|
~~ | 2187 | 2.5649426132440567 | 0.0011728132662295642 |
!! | 2187 | 2.2864151149988170 | 0.0010454572999537344 |
在JS中,因为有参数的转换,~~必然会存在性能的损耗,可是这个损耗在咱们能够接受的范围内,对比上面几组测试结果可得知,同是数字的状况下,~速度最快,而其余两种状况因为转换的缘由,略有下降。
在测试过程当中,还发现了一个有意思的事,发现用console、performance测量时,第一个耗时较长,有图有真相:
第一、2张图是console.time、performance.now计算的结果,在第二张图时还互换了下两个函数调用顺序。猜想是第一次调用console.time、performance须要进行某些初始化。