整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          JavaScript位運算及其妙用

          JavaScript位運算及其妙用

          代計算機的數(shù)據(jù)是以二進制的形式進行存儲的。 位運算就是直接對整數(shù)在內(nèi)存中的二進制位進行操作。使用位運算可以提高代碼性能,精簡代碼。

          在說位運算前,先簡單說說幾個概念,具體有機器數(shù)、真值、原碼、反碼、補碼

          機器數(shù)

          一個數(shù)在計算機中的二進制表示形式,叫做這個數(shù)的機器數(shù)。

          機器數(shù)是帶符號的,計算機中機器數(shù)的最高位是符號位, 正數(shù)為0, 負數(shù)為1。

          二進制的位數(shù)受機器設(shè)備的限制。機器內(nèi)部設(shè)備一次能表示的二進制位數(shù)叫機器的字長,一臺機器的字長是固定的。字長8位叫一個字節(jié)(Byte),機器字長一般都是字節(jié)的整數(shù)倍,如字長8位、16位、32位、64位。

          例如在8位機器中,+3轉(zhuǎn)為二進制為 00000011,-3轉(zhuǎn)為二進制為 10000011,其中00000011和10000011就是機器數(shù)。

          機器數(shù)的真值

          直接用正號“+”和負號“-”來表示其正負的二進制數(shù)叫做機器數(shù)的真值。

          例如“00000011”和“10000011”是兩個機器數(shù),而它們的真值分別為+0000011(即+3)和-0000011(即-3)。

          機器數(shù)包含原碼、反碼和補碼三種表示形式

          原碼

          將數(shù)的真值形式中“+”號用“0”表示,“-”號用“1”表示時,叫做數(shù)的原碼形式,簡稱原碼。

          也可理解為符號位加上真值的絕對值。第一位是符號位,其余位表示數(shù)值。

          例如:

          十進制

          真值

          原碼

          +3

          +0000011

          00000011

          -3

          -0000011

          10000011

          原碼的表示比較直觀,但是在進行加減法運算時,由于符號位的存在,使得機器的運算比較復(fù)雜,所以就引入了反碼和補碼。

          反碼

          對于正數(shù)來說,其反碼和原碼完全相同。

          對于負數(shù)來說,其反碼是在其原碼的基礎(chǔ)上, 符號位不變,其他位取反(0變1,1變0)。

          十進制

          真值

          原碼

          反碼

          +3

          +0000011

          00000011

          00000011

          -3

          -0000011

          10000011

          11111100

          補碼

          對于正數(shù)來說,其補碼和原碼完全相同。

          對于負數(shù)來說,其補碼是其反碼的末位加1,也就是說在其原碼的基礎(chǔ)上, 符號位不變,其他位取反后加1。

          十進制

          真值

          原碼

          反碼

          補碼

          +3

          +0000011

          00000011

          00000011

          00000011

          -3

          -0000011

          10000011

          11111100

          11111101

          注意:在計算機系統(tǒng)中,數(shù)值一律用補碼來表示和存儲。主要原因是為了便于處理和運算。

          具體為何需要引入反碼和補碼以及為何計算機系統(tǒng)中數(shù)值用補碼來表示和存儲,文章篇幅有限,此塊也不是本文的重點,詳細可以參考這篇文章www.cnblogs.com/zhangziqiu/…

          位運算符

          ? 在 JavaScript 中,數(shù)值是以64位浮點數(shù)的形式儲存。但是位運算符只對整數(shù)起作用。JavaScript在做位運算時,會事先將其轉(zhuǎn)換為32位有符號整型(用比特序列表示,即0和1組成)并開始計算,在得到結(jié)果后再將其轉(zhuǎn)回JavaScript的數(shù)值類型。

          ? 因為 js 的整數(shù)默認是帶符號數(shù),所以在位運算中,只能使用 31 位,開發(fā)者是不能訪問最高位的。在運算時,如果位數(shù)超過,那么超過的數(shù)字會被丟棄,這會造成各種錯誤的結(jié)果,并且沒有任何警告信息。所以,并不是所有計算都適合通過位運算符來計算。

          ? 位運算符操作的是數(shù)值對應(yīng)的機器數(shù),這個機器數(shù)的表示形式就是補碼的形式。(上面說了,為了便于處理和運算,計算機系統(tǒng)中,數(shù)值用補碼來表示和存儲。)

          ? JavaScript中的位運算符有以下:

          • & :與運算;兩個運算比較的bit位都為1時,這個bit位才為1,只要有任意一個位是0,這個位就是0
          console.log(5 & 3) // 輸出1
          // 5的機器數(shù)表示為 00000000 00000000 00000000 00000101
          // 3的機器數(shù)表示為 00000000 00000000 00000000 00000011
          // 所以計算結(jié)果為1 00000000 00000000 00000000 00000001

          :或運算;兩個運算比較的bit位只要有一個是1,這個bit位就是1

          console.log(5 | 3) // 輸出7
          // 5的機器數(shù)表示為 00000000 00000000 00000000 00000101
          // 3的機器數(shù)表示為 00000000 00000000 00000000 00000011
          // 所以計算結(jié)果為7 00000000 00000000 00000000 00000111

          ^ :異或運算;兩個運算比較的bit位相同則為0,不同則為1

          console.log(5 ^ 3) // 輸出6
          // 5的機器數(shù)表示為 00000000 00000000 00000000 00000101
          // 3的機器數(shù)表示為 00000000 00000000 00000000 00000011
          // 所以計算結(jié)果為6 00000000 00000000 00000000 00000110

          ~:取反運算;0變1,1變0

          console.log(~5) // 輸出-6
          // 5的機器數(shù)表示為	00000000 00000000 00000000 00000101
          // 取反之后表示為	11111111 11111111 11111111 11111010
          // 該數(shù)是負數(shù),負數(shù)補碼與原碼不同,先將其轉(zhuǎn)為原碼的反碼:11111111 11111111 11111111 11111001
          // 再轉(zhuǎn)為原碼:10000000 00000000 00000000 00000110
          // 所以結(jié)果為:-6

          << :左移;各二進位全部左移若干位,高位丟棄(即左邊超出的丟棄),低位補0(即右邊差的)

          console.log(5 << 3) // 輸出40
          // 5的機器數(shù)表示為  00000000 00000000 00000000 00000101
          // 左移三位結(jié)果為40 00000000 00000000 00000000 00101000

          圖示:


          console.log(-1073741822 << 2) // 輸出8
          // -1073741822的機器數(shù)表示為	 11000000 00000000 00000000 00000010		
          // 左移兩位結(jié)果為8            00000000 00000000 00000000 00001000

          圖示:

          >> :帶符號擴展右移;各二進位全部右移若干位,右邊被移出的丟棄,左邊會復(fù)制最左側(cè)的位來填充左側(cè)(這里最左側(cè)的位是符號位,也可以認為對于無符號數(shù),高位補 0,對于有符號數(shù),高位補符號位)。

          console.log(5 >> 2) // 輸出1
          // 5的機器數(shù)表示為	          00000000 00000000 00000000 00000101
          // 帶符號擴展右移兩位結(jié)果為1     00000000 00000000 00000000 00000001

          圖示:


          console.log(-5 >> 2) // 輸出-2
          // -5的機器數(shù)表示為:         11111111 11111111 11111111 11111011
          // 帶符號擴展右移兩位結(jié)果為:   11111111 11111111 11111111 11111110
          // 先將該值轉(zhuǎn)為原碼的反碼:    11111111 11111111 11111111 11111101
          // 再轉(zhuǎn)為原碼:		10000000 00000000 00000000 00000010
          // 所以結(jié)果為:-2

          圖示:

          >>> :無符號右移;各二進位全部右移若干位,右邊被移出的丟棄,左邊用0填充。因為符號位變成了 0,所以結(jié)果總是非負數(shù)。(即便右移 0 個比特,結(jié)果也是非負的。)

          console.log(5 >>> 2) // 輸出1
          // 5的機器數(shù)表示為	      00000000 00000000 00000000 00000101
          // 無符號右移兩位結(jié)果為1    00000000 00000000 00000000 00000001

          圖示:

          console.log(-5 >>> 2) // 輸出1073741822
          // -5的機器數(shù)表示為:                11111111 11111111 11111111 11111011
          // 無符號右移兩位結(jié)果為1073741822:    00111111 11111111 11111111 11111110


          位運算符的妙用

          位運算判斷奇數(shù)偶數(shù)

          根據(jù)數(shù)值的機器數(shù)最后一位是0還是1即可判斷,為0就是偶數(shù),為1就是奇數(shù)。

          function fun(num) {
            if ((num & 1)===0) {
              return "偶數(shù)";
            }
            if ((num & 1)===1) {
              return "奇數(shù)";
            }
          }
          console.log(fun(44)); // 輸出偶數(shù)
          console.log(fun(55)); // 輸出奇數(shù)

          位運算實現(xiàn)兩數(shù)交換

          不使用第三個變量,實現(xiàn)兩個數(shù)交換,可以通過以下方式實現(xiàn)

          a=a + b;
          b=a - b;
          a=a - b;
          // 或者
          a=[b, (b=a)][0];
          // 或者
          [a, b]=[b, a]

          根據(jù)異或運算的規(guī)則,可以得出一些特性

          // a取任何值,下面的結(jié)果都成立
          a^a=0
          a^0=a
          // 異或運算且滿足交換律和結(jié)合律,即
          a^b=b^a
          a^b^c=a^(b^c)=(a^c)^b
          // 其實與運算也是滿足交換律和結(jié)合律的
          a&b=b&a
          a&b&c=a&(b&c)=(a&c)&b
          // 于是就可以推出
          a^b^a=b^(a^a)=b^0=b
          // 如果 c=a^b 那么 a=c^b,這是因為 a=c^b=a^b^b=a^(b^b)=a^0=a
          // 如果 d=a^b^c 那么 a=d^b^c, 以此類推...

          那么根據(jù)以上的特性,就可以用位運算的方式實現(xiàn)兩數(shù)交換,如下

          a=a^b
          b=a^b
          a=a^b

          位運算實現(xiàn)乘除法

          將某個數(shù)值帶符號右移一位,相當(dāng)于該數(shù)值除以2;

          將某個數(shù)值左移一位,相當(dāng)于該數(shù)值除以2;

          console.log(6 >> 1) // 輸出3
          console.log(6 << 1) // 輸出12

          而右移n位,就相當(dāng)于該數(shù)值除以2的n次方

          而左移n位,就相當(dāng)于該數(shù)值乘以2的n次方

          console.log(32 >> 3) // 輸出4, 相當(dāng)于32除以2的3次方(8),等于4
          console.log(32 << 3) // 輸出256, 相當(dāng)于32乘以2的3次方(8),等于256

          但是用這種用法實現(xiàn)乘除法有很多問題,比如

          • 結(jié)果只會保留整數(shù)部分
          console.log(7 >> 1) // 輸出3

          如果數(shù)值操作影響到了符號位,結(jié)果會錯誤

          console.log(-2147483646 << 1) // 輸出4, 而-2147483646除以2的結(jié)果應(yīng)該是-1073741823

          位元算實現(xiàn)取整

          • 通過或運算實現(xiàn):將數(shù)值與0進行或運算從而得到該數(shù)值的整數(shù)部分
          console.log(5.2 | 0) // 輸出5
          console.log(-7.2 | 0) // 輸出-7

          通過與運算實現(xiàn):將數(shù)值與-1進行或運算從而得到該數(shù)值的整數(shù)部分

          console.log(5.2 & -1) // 輸出5
          console.log(-7.2 & -1) // 輸出-7

          通過異或運算實現(xiàn):

          • 將數(shù)值與0進行異或運算
          console.log(5.2 ^ 0);// 輸出5
          console.log(-7.2 ^ 0);// 輸出-7

          將數(shù)值與另一個數(shù)進行兩次異或運算

          console.log(5.2 ^ 2 ^ 2); // 輸出5
          console.log(-7.2 ^ 2 ^ 2);// 輸出-7

          通過取反運算實現(xiàn):將數(shù)值進行兩次取反運算

          console.log(~~5.2); // 輸出5
          console.log(~~-7.2); // 輸出-7

          通過左移或右移運算實現(xiàn):將數(shù)值左移或右移0位

          console.log(5.2 >> 0); // 輸出5
          console.log(-7.2 << 0); // 輸出-7

          位掩碼和權(quán)限管理

          位掩碼(BitMask),是”位(Bit)“和”掩碼(Mask)“的組合詞。”位“指代著二進制數(shù)據(jù)當(dāng)中的二進制位,而”掩碼“指的是一串用于與目標數(shù)據(jù)進行按位操作的二進制數(shù)字。組合起來,就是”用一串二進制數(shù)字(掩碼)去操作另一串二進制數(shù)字“的意思。

          位掩碼可以用于多種場景,比較常用的,我們可以用它來進行權(quán)限管理。

          假設(shè)我們在做一個直播教學(xué)系統(tǒng),每個學(xué)生都有其自己的狀態(tài),比如是否舉手,攝像頭狀態(tài),設(shè)備類型三個狀態(tài)。

          如果我們給每個學(xué)生設(shè)置三個狀態(tài)值,那會很麻煩(這里只是舉例三個,現(xiàn)實場景可能有七八個狀態(tài)值)

          那么我們可以設(shè)計每個學(xué)生一個狀態(tài)碼,例如這個狀態(tài)碼是一個9位的二進制數(shù)

          我們可以這個數(shù)上劃分每個狀態(tài)所占的位

          例如從最低位(按最低位0)開始

          • 低二位(從最低位(0)到第1位)表示學(xué)生的舉手狀態(tài)。舉手的話為 01,未舉手為 10,未知為 00
          • 從低位第二位到第四位表示該學(xué)生的攝像狀態(tài)。打開為001,關(guān)閉為010,未知為000,設(shè)備故障為011,被禁用為100
          • 從低位第五位到第八位表示該學(xué)生的設(shè)備類型。例如window是0001,mac是0010,ipad是0011,android是0100,ios是0111,未知是0000

          具體可以在代碼中定義一個枚舉:

          const enums={
            // 舉手狀態(tài)
            handsUp: {
              open: 0b01, // 舉手中
              close: 0b10, // 未舉手
              unknow: 0b00, // 未知
              lowBitPos: 0, // 從第幾位開始使用
              bitLength: 2, // 所占位的長度
            },
            // 攝像頭狀態(tài)
            camera: {
              open: 0b001, // 打開
              close: 0b010, // 關(guān)閉
              disable: 0b011, // 設(shè)備故障
              ban: 0b100, // 被禁用
              unknow: 0b000, // 未知
              lowBitPos: 2, // 從第幾位開始使用
              bitLength: 3, // 所占位的長度
            },
            // 設(shè)備類型
            device: {
              window: 0b0001, // window電腦
              mac: 0b0010, // 蘋果電腦
              ipad: 0b0011, // 蘋果平板
              android: 0b0100, // 安卓手機
              ios: 0b0111, // 蘋果手機
              unknow: 0b0000, // 未知
              lowBitPos: 5, // 從第幾位開始使用
              bitLength: 4, // 所占位的長度
            }
          };

          例如我隨便舉個例子,假設(shè)某個學(xué)生的狀態(tài)碼為 0b001001001

          那么其對應(yīng)狀態(tài)如下


          但是在代碼中,我們要如何獲取或修改學(xué)生狀態(tài)對應(yīng)的值呢?這就要用到前面所說的位掩碼的知識了

          獲取

          首先是獲取狀態(tài)值,我們需要從該狀態(tài)碼中截取出指定的從某一位到某一位的值。

          // 舉個例子,假設(shè)學(xué)生此時的狀態(tài)碼為0b001001001,獲取該學(xué)生的攝像頭狀態(tài)
          let state=0b001001001
          // 因為攝像頭狀態(tài)是從低位開始(0開始)第二位到第四位
          // 首先將狀態(tài)碼右移兩位
          let v=state >> enums.camera.lowBitPos  // enums上面定義了
          // 然后將轉(zhuǎn)換后的值與0b111進行與運算
          let result=v & 0b111
          console.log(result) // 輸出結(jié)果為3,也就是0b010,如此便獲取到了該學(xué)生的攝像頭狀態(tài)

          上面代碼中,因為攝像頭狀態(tài)是占了三個bit位,所以需要與0b111進行與運算來得到結(jié)果,但是舉手狀態(tài)是兩位,設(shè)備類型是四位,就要分別和0b11和0b1111進行與運算,這樣比較麻煩,所以上面的代碼可以優(yōu)化為

          let result=v & ((0b1 << enums.camera.bitLength) - 1)

          也可以將所有需要用到的位全是1的值先存在數(shù)組里面,方便后續(xù)運算

          const bitArr=[0b0]
          // 但位運算時僅支持 1 << 31大小
          for (let i=1; i < 32; i++) {
              bitArr.push((0b1 << i) - 1)
          }
          Object.freeze(bitArr) // 凍結(jié)數(shù)組  不允許修改
          
          let result=v & bitArr[enums.camera.bitLength]

          總結(jié)上面代碼,我們可以將獲取狀態(tài)值封裝為一個函數(shù)

          const getProvide=(state, lowBitPos, bitLength)=> {
             return (state >> lowBitPos) & bitArr[bitLength]
          }

          修改

          知道如何獲取以后,那么修改該如何呢

          例如,目前學(xué)生的狀態(tài)碼對應(yīng)的設(shè)置狀態(tài)為0b0010(蘋果電腦),我們要修改學(xué)生的設(shè)備狀態(tài)為0b0100(安卓手機)

          let state=0b001001001
          // 首先先獲取設(shè)備狀態(tài)對應(yīng)的值
          let deviceState=getProvide(state, enums.device.lowBitPos, enums.device.bitLength)
          // 然后先清除設(shè)備狀態(tài)對應(yīng)的位數(shù)區(qū)間
          let clearState=state - (deviceState << enums.device.lowBitPos)
          // 然后重新加上新的狀態(tài)區(qū)間即可
          let result=clearState + (enums.device.android << enums.device.lowBitPos)
          console.log(result.toString(2)) // 輸出 010001001

          封裝為函數(shù)如下

          const setProvide=(state, lowBitPos, bitLength, newSingleState)=> {
            const get=getProvide(state, lowBitPos, bitLength)
            let clearState=state - (get << lowBitPos)
            return clearState + (newSingleState << lowBitPos)
          }

          如此便實現(xiàn)了對學(xué)生用戶的權(quán)限狀態(tài)控制。

          者:平臺前端組

          轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/MbSTkkzf7nDjtBkmh7Iqbw

          者:Dunizb

          轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/kxo6tOmaJYdINiIOqls1LA


          主站蜘蛛池模板: 视频在线观看一区二区三区| 精品国产免费一区二区三区香蕉| 中文精品一区二区三区四区| 国产一区在线视频| 国产精品 一区 在线| av无码一区二区三区| 国产精品视频一区二区三区无码| 无码免费一区二区三区免费播放| 亚洲AV福利天堂一区二区三| 中文字幕精品一区二区| 国产一区二区三区播放| 亚洲乱码国产一区三区| 国产美女在线一区二区三区| 国产肥熟女视频一区二区三区| 亚洲毛片不卡av在线播放一区| 奇米精品一区二区三区在线观看| 中文字幕AV无码一区二区三区| 免费萌白酱国产一区二区| 国产精品免费视频一区| 精品成人一区二区三区四区| 2020天堂中文字幕一区在线观| 蜜桃AV抽搐高潮一区二区| 国产免费一区二区三区免费视频| 亚洲一区二区影院| 精品一区精品二区| 亚洲福利秒拍一区二区| 一区二区三区在线播放视频| 中文字幕日韩一区| 国产福利电影一区二区三区| 亚洲丰满熟女一区二区v| 一区二区三区精品高清视频免费在线播放 | 日韩精品一区二区三区毛片| 中文字幕在线播放一区| 亚洲大尺度无码无码专线一区| 亚洲av片一区二区三区| 亚洲国产AV一区二区三区四区| 国产日韩一区二区三免费高清| 欧洲精品码一区二区三区| 色欲精品国产一区二区三区AV| 无码人妻一区二区三区在线水卜樱| 亚洲AV无码一区二区三区牛牛|