介紹
按位非運(yùn)算符(~
)將操作數(shù)的位反轉(zhuǎn)。它將操作數(shù)轉(zhuǎn)化為 32 位的有符號(hào)整型。也就是可以對(duì)數(shù)字進(jìn)行取整操作(保留整數(shù)部分,舍棄小數(shù)部分)。
~-2 // 1
~-2.222 // 1
并且按位非運(yùn)算時(shí),任何數(shù)字?x
(已被轉(zhuǎn)化為 32 位有符號(hào)整型)?的運(yùn)算結(jié)果都是?-(x + 1)
。
那么雙重按位非(~~
)對(duì)數(shù)字的運(yùn)算結(jié)果就是?-(-(x + 1) + 1)
,結(jié)果就是 x
。
所以利用 ~~
操作數(shù)字時(shí)就可對(duì)其進(jìn)行取整操作(右移操作符 x >> 0
和按位或操作符 x | 0
也有相同作用)。
如果操作的不是 Number
類型的,操作的對(duì)象會(huì)先轉(zhuǎn)化 Number
類型,下面一起來(lái)看看。
操作原始數(shù)據(jù)類型時(shí)
~~(-2.999); // => -2
~~null; // => 0
~~undefined; // => 0
~~0; // => 0
~~(1/0); // => 0
~~false; // => 0
~~true; // => 1
~~'1234' // => 1234
~~'1234asdf' // => 0
~~NaN // => 0
~~
對(duì)于不能轉(zhuǎn)化為數(shù)字的數(shù)據(jù)(NaN
) ,操作的結(jié)果為 0
右移操作符 >>
和按位或操作符 |
也是如此。
(-2.999) >> 0 // => -2
null >> 0 // => 0
undefined >> 0 // => 0
0 >> 0 // => 0
(1/0) >> 0 // => 0
false >> 0 // => 0
true >> 0 // => 1
'1234' >> 0 // => 1234
'1234asdf' >> 0 // => 0
NaN >> 0 // => 0
(-2.999) | 0 // => -2
null | 0 // => 0
undefined | 0 // => 0
0 | 0 // => 0
(1/0) | 0 // => 0
false | 0 // => 0
true | 0 // => 1
'1234' | 0 // => 1234
'1234asdf' | 0 // => 0
NaN | 0 // => 0
操作對(duì)象數(shù)據(jù)類型時(shí)
當(dāng) ~~
作用于對(duì)象類型時(shí),對(duì)象類型會(huì)先隱式轉(zhuǎn)化為數(shù)字,轉(zhuǎn)化的結(jié)果取決于對(duì)象的 valueOf
方法和 toString
方法返回的結(jié)果。如果對(duì)象類型轉(zhuǎn)化后最終的結(jié)果是 NaN
,那么 ~~
操作 NaN
則會(huì)直接返回 0
。
詳細(xì)的轉(zhuǎn)換過(guò)程:
調(diào)用對(duì)象的
valueOf
方法:
如果該方法返回一個(gè)原始值,JavaScript會(huì)嘗試將這個(gè)原始值轉(zhuǎn)換為一個(gè)數(shù)字。如果valueOf
方法返回的還是一個(gè)對(duì)象,JavaScript會(huì)繼續(xù)調(diào)用對(duì)象的toString
方法。調(diào)用對(duì)象的
toString
方法:
這個(gè)方法返回一個(gè)字符串,然后JavaScript會(huì)嘗試將這個(gè)字符串轉(zhuǎn)換為一個(gè)數(shù)字。轉(zhuǎn)換字符串為數(shù)字:
一旦從valueOf
或toString
方法中獲得了一個(gè)原始值(通常是字符串),JavaScript會(huì)按照字符串到數(shù)字的轉(zhuǎn)換規(guī)則來(lái)處理這個(gè)值。
如果valueOf
或toString
返回的值是NaN
,那么結(jié)果就是NaN
。如果字符串不能被解析為一個(gè)有效的數(shù)字,結(jié)果也是NaN
。 ~~
操作 NaN
返回 0
。
所以也就有下面的結(jié)果:
~~{}; // => 0
~~{a:1} // => 0
~~[]; // => 0
~~[1]; // => 1
~~[1,2]; // => 0
對(duì)于數(shù)組而言,將數(shù)組轉(zhuǎn)化為數(shù)字,會(huì)調(diào)用數(shù)組的 toString()
。
數(shù)組的?
toString
?方法實(shí)際上在內(nèi)部調(diào)用了?join()
?方法來(lái)拼接數(shù)組并返回一個(gè)包含所有數(shù)組元素的字符串,元素之間用逗號(hào)分隔。如果?join
?方法不可用或者不是函數(shù),則會(huì)使用?Object.prototype.toString
?來(lái)代替,并返回?[object Array]
。
上面的 [1,2] 經(jīng)過(guò) toString()
后是 '1,2'
, 轉(zhuǎn)為數(shù)字則是 NaN
。所以 ~~[1,2]
結(jié)果為 0。
下面是對(duì)象有自定義的 valueOf()
或者 toString()
情況
var a = {
valueOf:function(){return '11'}, // 字符串'11' 可被轉(zhuǎn)化為 數(shù)字 11
toString:function(){return 12}
}
~~a // => 11
var b = {
valueOf:function(){return 'asdf'}, // 字符串'asdf' 轉(zhuǎn)化為 NaN
toString:function(){return 12}
}
~~b // => 0
var c = {
toString:function(){return 12} // 沒(méi)有 valueOf() ,則調(diào)用 toString()
}
~~c // => 12
var d = {
toString:function(){return 'asdf'} // 字符串'asdf' 轉(zhuǎn)化為 NaN
}
~~d // => 0
可進(jìn)行運(yùn)算的數(shù)字的有效范圍
由于 按位運(yùn)算總是將操作數(shù)轉(zhuǎn)換為 32 位整數(shù)。 超過(guò) 32 位的數(shù)字將丟棄其最高有效位。如下例子中(來(lái)自MDN),超過(guò) 32 位的整數(shù)將轉(zhuǎn)換為 32 位整數(shù):
Before: 11100110111110100000000000000110000000000001
After: 10100000000000000110000000000001
再比如 ~~
操作日期類型數(shù)據(jù),Date
的 valueOf
?方法返回以數(shù)值格式表示的一個(gè)?Date
?對(duì)象的原始值,從 1970 年 1 月 1 日 0 時(shí) 0 分 0 秒到該日期對(duì)象所代表時(shí)間的毫秒數(shù)。
返回的毫秒數(shù)是超過(guò) 32 位的整數(shù),不在 ~~
操作的有效范圍內(nèi),結(jié)果就不會(huì)是期望的那樣。
var date = new Date()
Number(date) // 1706671595364
~~date // 1569578852 結(jié)果失真
所以只有對(duì) 32位浮點(diǎn)數(shù)(經(jīng)測(cè)試,有效范圍為:[-2^31,2^31-1]
,即[-2147483648,2147483647]
) 進(jìn)行按位運(yùn)算時(shí)才會(huì)得到期望的結(jié)果。
~~2147483647.1 // => 2147483647 正確
~~2147483648.1 // => -2147483648 不正確
~~-2147483648.1 // => -2147483648 正確
~~-2147483649.1 // => 2147483647 不正確
需要注意的是,如果整數(shù)部分和小數(shù)部分?jǐn)?shù)字之和超過(guò)了 16 位(不包括小數(shù)點(diǎn)),那么雙重按位非操作符的結(jié)果也會(huì)不正確:( Number編碼的精度 的算術(shù)會(huì)受到舍入的影響。)
使用場(chǎng)景
對(duì)一些函數(shù)入?yún)⑿r?yàn)及處理方面有用,比如傳入的可能是任意數(shù)字,需要排除掉極端情況(NaN
,Infinity
),然后取整;
function fn(){
var param = arguments[1]
if(param === 'number' && !isNaN(foo) && foo !== Infinity){
var value = Number(param) || 0;
value = (value < 0)
? Math.ceil(value)
: Math.floor(value);
}
}
使用 ~~
后:
function fn(){
var value = ~~arguments[1]
}
拓展
左移(<<)和右移(>>)運(yùn)算符可以用來(lái)進(jìn)行快速的二進(jìn)制乘法和除法操作。左移一個(gè)數(shù)值實(shí)際上等于將這個(gè)數(shù)值乘以2的某個(gè)冪,而右移一個(gè)數(shù)值則等于將這個(gè)數(shù)值除以2的某個(gè)冪(忽略余數(shù))。
但這個(gè)使用場(chǎng)景只適用對(duì) 2 的乘法除法操作(。。。)
let result = 6 * 2; // 結(jié)果是12
let base = 6; // 二進(jìn)制表示為 110
let shift = 1; // 左移1位
let result = base << shift; // 結(jié)果是12,二進(jìn)制表示為 1100
let result = 12 / 2; // 結(jié)果是6,但可能得到一個(gè)浮點(diǎn)數(shù) let roundedResult = Math.floor(12 / 2); // 結(jié)果是6,確保得到整數(shù)
let base = 12; // 二進(jìn)制表示為 1100
let shift = 1; // 右移1位
let result = base >> shift; // 結(jié)果是6,二進(jìn)制表示為 110
總結(jié)
本文探討了使用雙重按位非運(yùn)算符 ~~
對(duì)操作數(shù)取整的原理。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-825089.html
-
~~
之所以可以用來(lái)取整,是因?yàn)榘次贿\(yùn)算操作數(shù)轉(zhuǎn)化為 32 位的有符號(hào)整型,會(huì)舍棄掉小數(shù)部分。并且按位非運(yùn)算(~
)時(shí),任何數(shù)字?x
(已被轉(zhuǎn)化為 32 位有符號(hào)整型)?的運(yùn)算結(jié)果都是?-(x + 1)
?。那么雙重按位非(~~
)對(duì)數(shù)字的運(yùn)算結(jié)果就是?-(-(x + 1) + 1)
,結(jié)果就是?x
?。 - 操作數(shù)是數(shù)字并且在位運(yùn)算的有效范圍內(nèi)(
[-2^31,2^31-1]
),~~
取整才會(huì)得到期望的結(jié)果。 - 使用場(chǎng)景方面對(duì)一些函數(shù)入?yún)⑿r?yàn)及處理方面可能有用
折騰完畢 ??。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-825089.html
到了這里,關(guān)于雙重按位非運(yùn)算符 ~~ 對(duì)數(shù)字取整的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!