JavaScript 運(yùn)算符

2021-09-15 15:12 更新

運(yùn)算符是用來處理數(shù)據(jù)的基本方法。JavaScript與其他編程語言一樣,提供了多種運(yùn)算符。

算術(shù)運(yùn)算符

JavaScript提供9個(gè)算術(shù)運(yùn)算符。

  • 加法運(yùn)算符(Addition):x + y

  • 減法運(yùn)算符(Subtraction): x - y

  • 乘法運(yùn)算符(Multiplication): x * y

  • 除法運(yùn)算符(Division):x / y

  • 余數(shù)運(yùn)算符(Remainder):x % y

  • 自增運(yùn)算符(Increment):++x 或者 x++

  • 自減運(yùn)算符(Decrement):--x 或者 x--

  • 求負(fù)運(yùn)算符(Negate):-x

  • 數(shù)值運(yùn)算符(Convert to number): +x

加法運(yùn)算符

加法運(yùn)算符(+)需要注意的地方是,它除了用于數(shù)值的相加,還能用于字符串的連接。

2014-02-19

1 + 1 // 2
"1" + "1" // "11"

兩個(gè)運(yùn)算子之中只要有一個(gè)是字符串,加法運(yùn)算符號(hào)就會(huì)變?yōu)樽址B接運(yùn)算符,返回連接后的字符串,其他情況則是返回?cái)?shù)值相加的和。這種由于參數(shù)不同,而改變自身行為的現(xiàn)象,叫做“重載”(overload)。

1 + "1" // "11"

上面代碼表示,兩個(gè)運(yùn)算子之中有一個(gè)是字符串,另一個(gè)運(yùn)算子就會(huì)被自動(dòng)轉(zhuǎn)為字符串。

"3" + 4 + 5 // "345"
3 + 4 + "5" // "75"

上面代碼中,由于加法運(yùn)算符遇到字符串,會(huì)發(fā)生重載,導(dǎo)致運(yùn)算結(jié)果的不同。

由于這個(gè)特性,下面的寫法有時(shí)用于將一個(gè)值轉(zhuǎn)為字符串。

x + ""

上面代碼表示,一個(gè)值加上空字符串,會(huì)使得該值轉(zhuǎn)為字符串形式。

布爾值和復(fù)合類型的值,也可以使用加法運(yùn)算符,但是會(huì)導(dǎo)致數(shù)據(jù)類型的自動(dòng)轉(zhuǎn)換,關(guān)于這方面的詳細(xì)討論,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。

加法運(yùn)算符以外的其他算術(shù)運(yùn)算符,都不會(huì)發(fā)生重載。它們的規(guī)則是:所有運(yùn)算子一律轉(zhuǎn)為數(shù)值,再進(jìn)行相應(yīng)的數(shù)學(xué)運(yùn)算。

1 - "1" // 0
+"3" // 3
-true // -1

上面代碼表示,減法運(yùn)算符將字符串“1”自動(dòng)轉(zhuǎn)為數(shù)值1,數(shù)值運(yùn)算符(+)將字符串“3”轉(zhuǎn)為數(shù)值3,求負(fù)運(yùn)算符(-)將布爾值true轉(zhuǎn)為-1。

由于加法運(yùn)算符與其他算術(shù)運(yùn)算符的這種差異,會(huì)導(dǎo)致一些意想不到的結(jié)果,計(jì)算時(shí)要小心。

var now = new Date();
typeof (now + 1) // "string"
typeof (now - 1) // "number"

上面代碼中,now是一個(gè)Date對(duì)象的實(shí)例。加法運(yùn)算時(shí),now轉(zhuǎn)為字符串,加一個(gè)數(shù)字,得到還是字符串;減法運(yùn)算時(shí),now轉(zhuǎn)為數(shù)值,減一個(gè)數(shù)字,得到的是數(shù)字。

余數(shù)運(yùn)算符

余數(shù)運(yùn)算符返回前一個(gè)運(yùn)算子被后一個(gè)運(yùn)算子除,所得的余數(shù)。

12 % 5 // 2

需要注意的是,運(yùn)算結(jié)果的正負(fù)號(hào)由第一個(gè)運(yùn)算子的正負(fù)號(hào)決定。

-1 % 2 // -1
1 % -2 // 1

為了得到正確的負(fù)數(shù)的余數(shù)值,需要先使用絕對(duì)值函數(shù)。

// 錯(cuò)誤的寫法
function isOdd(n) {
    return n % 2 === 1;
}
isOdd(-5) // false
isOdd(-4) // false

// 正確的寫法
function isOdd(n) {
    return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false

余數(shù)運(yùn)算符還可以用于浮點(diǎn)數(shù)的運(yùn)算。但是,由于浮點(diǎn)數(shù)不是精確的值,無法得到完全準(zhǔn)確的結(jié)果。

6.5 % 2.1 
// 0.19999999999999973

自增和自減運(yùn)算符

自增和自減運(yùn)算符,是一元運(yùn)算符,只需要一個(gè)運(yùn)算子。它們的作用是將運(yùn)算子首先轉(zhuǎn)為數(shù)值,然后加上1或者減去1。

var x = "1";
++x // 2

上面代碼的x是一個(gè)字符串,使用遞增運(yùn)算符后,x首先被轉(zhuǎn)為數(shù)值1,然后進(jìn)行遞增,返回2。

它們有一個(gè)需要注意的地方,就是放在變量之后,表示先返回變量操作前的值,再進(jìn)行遞增/遞減操作;放在變量之前,表示先進(jìn)行遞增/遞減操作,再返回變量操作后的值。

var x1 = 1;
var x2 = 1;

x1++ // 1
++x2 // 2

上面代碼中,x1是先返回后遞增,所以得到1;x2是先遞增后返回,所以得到2。

數(shù)值運(yùn)算符

數(shù)值運(yùn)算符(+)同樣使用加號(hào),但是加法運(yùn)算符是二元運(yùn)算符(需要兩個(gè)操作數(shù)),它是一元運(yùn)算符(只需要一個(gè)操作數(shù))。

它的重要作用在于可以將任何值轉(zhuǎn)為數(shù)值(與Number函數(shù)的作用相同)。

+true // 1
+[] // 0
+{} // NaN

上面代碼表示,非數(shù)值類型的值經(jīng)過數(shù)值運(yùn)算符以后,都變成了數(shù)值(包括最后一個(gè)NaN)。具體的類型轉(zhuǎn)換規(guī)則,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。

求負(fù)運(yùn)算符(-),也同樣具有將一個(gè)值轉(zhuǎn)為數(shù)值的功能,所以下面的寫法等同于求值運(yùn)算符。

-(-x)

上面代碼的圓括號(hào)不可少,否則會(huì)變成遞減運(yùn)算符。

賦值運(yùn)算符

賦值運(yùn)算符(Assignment Operators)用于給變量賦值。

最常見的賦值運(yùn)算符,當(dāng)然就是等號(hào)(=),表達(dá)式x=y表示將y賦值給x。除此之外,JavaScript還提供其他11個(gè)賦值運(yùn)算符。

x += y // 等同于 x = x + y
x -= y // 等同于 x = x - y
x *= y // 等同于 x = x * y
x /= y // 等同于 x = x / y
x %= y // 等同于 x = x % y
x >>= y // 等同于 x = x >> y
x <<= y // 等同于 x = x << y
x >>>= y // 等同于 x = x >>> y
x &= y // 等同于 x = x & y
x |= y // 等同于 x = x | y
x ^= y // 等同于 x = x ^ y

比較運(yùn)算符

比較運(yùn)算符比較兩個(gè)值,然后返回一個(gè)布爾值,表示是否滿足比較條件。JavaScript提供了8個(gè)比較運(yùn)算符。

  • == 相等
  • === 嚴(yán)格相等
  • != 不相等
  • !== 嚴(yán)格不相等
  •  小于
  •  小于或等于
  •  大于

  • = 大于或等于

其中,比較兩個(gè)值是否相等的運(yùn)算符有兩個(gè):一個(gè)是相等運(yùn)算符(==),另一個(gè)是嚴(yán)格相等運(yùn)算符(===)。

相等運(yùn)算符(==)比較兩個(gè)“值”是否相等,嚴(yán)格相等運(yùn)算符(===)比較它們是否為“同一個(gè)值”。兩者的一個(gè)重要區(qū)別是,如果兩個(gè)值不是同一類型,嚴(yán)格相等運(yùn)算符(===)直接返回false,而相等運(yùn)算符(==)會(huì)將它們轉(zhuǎn)化成同一個(gè)類型,再用嚴(yán)格相等運(yùn)算符進(jìn)行比較。

我們先看嚴(yán)格相等運(yùn)算符。

嚴(yán)格相等運(yùn)算符

嚴(yán)格相等運(yùn)算符的運(yùn)算規(guī)則如下:

(1)不同類型的值

如果兩個(gè)值的類型不同,直接返回false。

1 === "1" // false

true === "true" // false

上面代碼比較數(shù)值的1與字符串的“1”、布爾值的true與字符串“true”,因?yàn)轭愋筒煌Y(jié)果都是false。

(2)同一類的原始類型值

同一類型的原始類型的值(數(shù)值、字符串、布爾值)比較時(shí),值相同就返回true,值不同就返回false。

1 === 0x1 // true

上面代碼比較十進(jìn)制的1與十六進(jìn)制的1,因?yàn)轭愋秃椭刀枷嗤祷豻rue。

需要注意的是,NaN與任何值都不相等(包括自身)。另外,正0等于負(fù)0。

NaN === NaN  // false

+0 === -0 // true

(3)同一類的復(fù)合類型值

兩個(gè)復(fù)合類型(對(duì)象、數(shù)組、函數(shù))的數(shù)據(jù)比較時(shí),不是比較它們的值是否相等,而是比較它們是否指向同一個(gè)對(duì)象。

({}) === {} // false

[] === [] // false

(function (){}) === function (){} // false

上面代碼分別比較兩個(gè)空對(duì)象、兩個(gè)空數(shù)組、兩個(gè)空函數(shù),結(jié)果都是不相等。原因是對(duì)于復(fù)合類型的值,嚴(yán)格相等運(yùn)算比較的是它們的內(nèi)存地址是否一樣,而上面代碼中空對(duì)象、空數(shù)組、空函數(shù)的值,都存放在不同的內(nèi)存地址,結(jié)果當(dāng)然是false。另外,之所以要把第一個(gè)空對(duì)象放在括號(hào)內(nèi),是為了避免JavaScript引擎把這一行解釋成代碼塊,從而報(bào)錯(cuò);把第一個(gè)空函數(shù)放在括號(hào)內(nèi),是為了避免這一行被解釋成函數(shù)的定義。

如果兩個(gè)變量指向同一個(gè)復(fù)合類型的數(shù)據(jù),則它們相等。

var v1 = {};
var v2 = v1;

v1 === v2 // true

(4)undefined和null

undefined 和 null 與自身嚴(yán)格相等。

undefined === undefined // true

null === null // true

由于變量聲明后默認(rèn)值是undefined,因此兩個(gè)只聲明未賦值的變量是相等的。

var v1;
var v2;

v1 === v2 // true

(5)嚴(yán)格不相等運(yùn)算符

嚴(yán)格相等運(yùn)算符有一個(gè)對(duì)應(yīng)的“嚴(yán)格不相等運(yùn)算符”(!==),兩者的運(yùn)算結(jié)果正好相反。

1 !== "1" // true

相等運(yùn)算符

相等運(yùn)算符在比較相同類型的數(shù)據(jù)時(shí),與嚴(yán)格相等運(yùn)算符完全一樣。

在比較不同類型的數(shù)據(jù)時(shí),相等運(yùn)算符會(huì)先將數(shù)據(jù)進(jìn)行類型轉(zhuǎn)換,然后再用嚴(yán)格相等運(yùn)算符比較。類型轉(zhuǎn)換規(guī)則如下:

(1)原始類型的值

原始類型的數(shù)據(jù)會(huì)轉(zhuǎn)換成數(shù)值類型再進(jìn)行比較。

1 == true // true
0 == false // true

"true" == true // false

'' == 0 // true

'' == false  // true
'1' == true  // true

"2" == true // false
2 == true // false
2 == false // false

'\n  123  \t' == 123 // true
// 因?yàn)樽址D(zhuǎn)為數(shù)字時(shí),省略前置和后置的空格

上面代碼將字符串和布爾值都轉(zhuǎn)為數(shù)值,然后再進(jìn)行比較。字符串與布爾值的類型轉(zhuǎn)換規(guī)則,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。

(2)對(duì)象與原始類型值比較

對(duì)象(這里指廣義的對(duì)象,包括數(shù)值和函數(shù))與原始類型的值比較時(shí),對(duì)象轉(zhuǎn)化成原始類型的值,再進(jìn)行比較。

[1] == 1 // true
[1] == "1" // true
[1] == true // true

上面代碼將只含有數(shù)值1的數(shù)組與原始類型的值進(jìn)行比較,數(shù)組[1]會(huì)被自動(dòng)轉(zhuǎn)換成數(shù)值1,因此結(jié)果都是true。數(shù)組的類型轉(zhuǎn)換規(guī)則,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。

(3)undefined和null

undefined和null與其他類型的值比較時(shí),結(jié)果都為false,它們互相比較時(shí)結(jié)果為true。

false == null // false
0 == null // false

undefined == null // true

(4)相等運(yùn)算符的缺點(diǎn)

相等運(yùn)算符隱藏的類型轉(zhuǎn)換,會(huì)帶來一些違反直覺的結(jié)果。

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

上面這些表達(dá)式都很容易出錯(cuò),因此不要使用相等運(yùn)算符(==),最好只使用嚴(yán)格相等運(yùn)算符(===)。

(5)不相等運(yùn)算符

相等運(yùn)算符有一個(gè)對(duì)應(yīng)的“不相等運(yùn)算符”(!=),兩者的運(yùn)算結(jié)果正好相反。

1 != "1" // false

布爾運(yùn)算符

布爾運(yùn)算符用于將表達(dá)式轉(zhuǎn)為布爾值。

取反運(yùn)算符(!)

取反運(yùn)算符形式上是一個(gè)感嘆號(hào),用于將布爾值變?yōu)橄喾粗担磘rue變成false,false變成true。

!true // false
!false // true

對(duì)于非布爾值的數(shù)據(jù),取反運(yùn)算符會(huì)自動(dòng)將其轉(zhuǎn)為布爾值。規(guī)則是,以下六個(gè)值取反后為true,其他值取反后都為false。

  • undefined
  • null
  • false
  • 0(包括+0和-0)
  • NaN
  • 空字符串("")

這意味著,取反運(yùn)算符有轉(zhuǎn)換數(shù)據(jù)類型的作用。

!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true

!54 // false
!'hello' // false
![] // false
!{} // false

上面代碼中,不管什么類型的值,經(jīng)過取反運(yùn)算后,都變成了布爾值。

如果對(duì)一個(gè)值連續(xù)做兩次取反運(yùn)算,等于將其轉(zhuǎn)為對(duì)應(yīng)的布爾值,與Boolean函數(shù)的作用相同。這是一種常用的類型轉(zhuǎn)換的寫法。

!!x

// 等同于

Boolean(x)

上面代碼中,不管x是什么類型的值,經(jīng)過兩次取反運(yùn)算后,變成了與Boolean函數(shù)結(jié)果相同的布爾值。所以,兩次取反就是將一個(gè)值轉(zhuǎn)為布爾值的簡(jiǎn)便寫法。

取反運(yùn)算符的這種將任意數(shù)據(jù)自動(dòng)轉(zhuǎn)為布爾值的功能,對(duì)下面三種布爾運(yùn)算符(且運(yùn)算符、或運(yùn)算符、三元條件運(yùn)算符)都成立。

且運(yùn)算符(&&)

且運(yùn)算符的運(yùn)算規(guī)則是:如果第一個(gè)運(yùn)算子的布爾值為true,則返回第二個(gè)運(yùn)算子的值(注意是值,不是布爾值);如果第一個(gè)運(yùn)算子的布爾值為false,則直接返回第一個(gè)運(yùn)算子的值,且不再對(duì)第二個(gè)運(yùn)算子求值。

"t" && "" // ""
"t" && "f" // "f"
"t" && (1+2) // 3
"" && "f" // ""
"" && "" // ""

var x = 1;
(1-1) && (x+=1) // 0
x // 1

上面代碼的最后一部分表示,由于且運(yùn)算符的第一個(gè)運(yùn)算子的布爾值為false,則直接返回它的值0,而不再對(duì)第二個(gè)運(yùn)算子求值,所以變量x的值沒變。

這種跳過第二個(gè)運(yùn)算子的機(jī)制,被稱為“短路”。有些程序員喜歡用它取代if結(jié)構(gòu),比如下面是一段if結(jié)構(gòu)的代碼,就可以用且運(yùn)算符改寫。

if (i !== 0 ){
    doSomething();
}

// 等價(jià)于

i && doSomething();

上面代碼的兩種寫法是等價(jià)的,但是后一種不容易看出目的,也不容易除錯(cuò),建議謹(jǐn)慎使用。

且運(yùn)算符可以多個(gè)連用,這時(shí)返回第一個(gè)布爾值為false的表達(dá)式的值。

true && 'foo' && '' && 4 && 'foo' && true
// ''

上面代碼中第一個(gè)布爾值為false的表達(dá)式為第三個(gè)表達(dá)式,所以得到一個(gè)空字符串。

或運(yùn)算符(||)

或運(yùn)算符的運(yùn)算規(guī)則是:如果第一個(gè)運(yùn)算子的布爾值為true,則返回第一個(gè)運(yùn)算子的值,且不再對(duì)第二個(gè)運(yùn)算子求值;如果第一個(gè)運(yùn)算子的布爾值為false,則返回第二個(gè)運(yùn)算子的值。

"t" || "" // "t"
"t" || "f" // "t"
"" || "f" // "f"
"" || "" // ""

短路規(guī)則對(duì)這個(gè)運(yùn)算符也適用。

或運(yùn)算符可以多個(gè)連用,這時(shí)返回第一個(gè)布爾值為true的表達(dá)式的值。

false || 0 || '' || 4 || 'foo' || true
// 4

上面代碼中第一個(gè)布爾值為true的表達(dá)式是第四個(gè)表達(dá)式,所以得到數(shù)值4。

或運(yùn)算符常用于為一個(gè)變量設(shè)置默認(rèn)值。

function saveText(text) {
    text = text || '';
    // ...
}

// 或者寫成

saveText(this.text||'')

上面代碼表示,如果函數(shù)調(diào)用時(shí),沒有提供參數(shù),則該參數(shù)默認(rèn)設(shè)置為空字符串。

三元條件運(yùn)算符( ? :)

三元條件運(yùn)算符用問號(hào)(?)和冒號(hào)(:),分隔三個(gè)表達(dá)式。如果第一個(gè)表達(dá)式的布爾值為true,則返回第二個(gè)表達(dá)式的值,否則返回第三個(gè)表達(dá)式的值。

"t" ? true : false // true

0 ? true : false // false

上面代碼的“t”和0的布爾值分別為true和false,所以分別返回第二個(gè)和第三個(gè)表達(dá)式的值。

通常來說,三元條件表達(dá)式與if...else語句具有同樣表達(dá)效果,前者可以表達(dá)的,后者也能表達(dá)。但是兩者具有一個(gè)重大差別,if...else是語句,沒有返回值;三元條件表達(dá)式是表達(dá)式,具有返回值。所以,在需要返回值的場(chǎng)合,只能使用三元條件表達(dá)式,而不能使用if..else。

var check = true ? console.log('T') : console.log('F');

console.log(true ? 'T' : 'F');

上面代碼是賦值語句和console.log方法的例子,它們都需要使用表達(dá)式,這時(shí)三元條件表達(dá)式就能滿足需要。如果要用if...else語句,就必須改變整個(gè)代碼寫法了。

位運(yùn)算符

簡(jiǎn)介

位運(yùn)算符用于直接對(duì)二進(jìn)制位進(jìn)行計(jì)算,一共有7個(gè)。

  • 或運(yùn)算(or):符號(hào)為|,表示兩個(gè)二進(jìn)制位中有一個(gè)為1,則結(jié)果為1,否則為0。

  • 與運(yùn)算(and):符號(hào)為&,表示兩個(gè)二進(jìn)制位都為1,則結(jié)果為1,否則為0。

  • 否運(yùn)算(not):符號(hào)為~,表示將一個(gè)二進(jìn)制位變成相反值。

  • 異或運(yùn)算(xor):符號(hào)為?,表示兩個(gè)二進(jìn)制位中有且僅有一個(gè)為1時(shí),結(jié)果為1,否則為0。

  • 左移運(yùn)算(left shift):符號(hào)為<<,詳見下文解釋。

  • 右移運(yùn)算(right shift):符號(hào)為>>,詳見下文解釋。

  • 帶符號(hào)位的右移運(yùn)算(zero filled right shift):符號(hào)為>>>,詳見下文解釋。

這些位運(yùn)算符直接處理每一個(gè)比特位,所以是非常底層的運(yùn)算,好處是速度極快,缺點(diǎn)是很不直觀,許多場(chǎng)合不能使用它們,否則會(huì)帶來過度的復(fù)雜性。

有一點(diǎn)需要特別注意,位運(yùn)算符只對(duì)整數(shù)起作用,如果一個(gè)運(yùn)算子不是整數(shù),會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再運(yùn)行。另外,雖然在JavaScript內(nèi)部,數(shù)值都是以64位浮點(diǎn)數(shù)的形式儲(chǔ)存,但是做位運(yùn)算的時(shí)候,是以32位帶符號(hào)的整數(shù)進(jìn)行運(yùn)算的,并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)。

i = i|0;

上面這行代碼的意思,就是將i轉(zhuǎn)為32位整數(shù)。

“或運(yùn)算”與“與運(yùn)算”

這兩種運(yùn)算比較容易理解,就是逐位比較兩個(gè)運(yùn)算子?!盎蜻\(yùn)算”的規(guī)則是,如果兩個(gè)二進(jìn)制位之中至少有一個(gè)位為1,則返回1,否則返回0。“與運(yùn)算”的規(guī)則是,如果兩個(gè)二進(jìn)制位之中至少有一個(gè)位1為0,則返回0,否則返回1。

0 | 3 // 3
0 & 3 // 0

上面兩個(gè)表達(dá)式,0和3的二進(jìn)制形式分別是00和11,所以進(jìn)行“或運(yùn)算”會(huì)得到11(即3),進(jìn)行”與運(yùn)算“會(huì)得到00(即0)。

位運(yùn)算只對(duì)整數(shù)有效,遇到小數(shù)時(shí),會(huì)將小數(shù)部分舍去,只保留整數(shù)部分。所以,將一個(gè)小數(shù)與0進(jìn)行或運(yùn)算,等同于對(duì)該數(shù)去除小數(shù)部分,即取整數(shù)位。

2.9 | 0
// 2

-2.9 | 0
// -2

需要注意的是,這種取整方法不適用超過32位整數(shù)最大值2147483647的數(shù)。

2147483649.4 | 0;
// -2147483647

否運(yùn)算

“否運(yùn)算”將每個(gè)二進(jìn)制位都變?yōu)橄喾粗担?變?yōu)?,1變?yōu)?)。它的返回結(jié)果有時(shí)比較難理解,因?yàn)樯婕暗接?jì)算機(jī)內(nèi)部的數(shù)值表示機(jī)制。

~ 3 // -4

上面表達(dá)式對(duì)3進(jìn)行“否運(yùn)算”,得到-4。之所以會(huì)有這樣的結(jié)果,是因?yàn)槲贿\(yùn)算時(shí),JavaScirpt內(nèi)部將所有的運(yùn)算子都轉(zhuǎn)為32位的二進(jìn)制整數(shù)再進(jìn)行運(yùn)算。3在JavaScript內(nèi)部是00000000000000000000000000000011,否運(yùn)算以后得到11111111111111111111111111111100,由于第一位是1,所以這個(gè)數(shù)是一個(gè)負(fù)數(shù)。JavaScript內(nèi)部采用2的補(bǔ)碼形式表示負(fù)數(shù),即需要將這個(gè)數(shù)減去1,再取一次反,然后加上負(fù)號(hào),才能得到這個(gè)負(fù)數(shù)對(duì)應(yīng)的10進(jìn)制值。這個(gè)數(shù)減去1等于11111111111111111111111111111011,再取一次反得到00000000000000000000000000000100,再加上負(fù)號(hào)就是-4??紤]到這樣的過程比較麻煩,可以簡(jiǎn)單記憶成,一個(gè)數(shù)與自身的取反值相加,等于-1。

~ -3 // 2

上面表達(dá)式可以這樣算,-3的取反值等于-1減去-3,結(jié)果為2。

對(duì)一個(gè)整數(shù)連續(xù)兩次“否運(yùn)算”,得到它自身。

~~3 // 3

所有的位運(yùn)算都只對(duì)整數(shù)有效。否運(yùn)算遇到小數(shù)時(shí),也會(huì)將小數(shù)部分舍去,只保留整數(shù)部分。所以,對(duì)一個(gè)小數(shù)連續(xù)進(jìn)行兩次否運(yùn)算,能達(dá)到取整效果。

~~2.9
// 2

使用否運(yùn)算取整,是所有取整方法中最快的一種。

對(duì)字符串進(jìn)行否運(yùn)算,JavaScript引擎會(huì)先調(diào)用Number函數(shù),將字符串轉(zhuǎn)為數(shù)值。

// 以下例子相當(dāng)于~Number('011')
~'011'  // -12
~'42 cats' // -1
~'0xcafebabe' // 889275713
~'deadbeef' // -1

// 以下例子相當(dāng)于~~Number('011')
~~'011';        // 11        
~~'42 cats';    // 0
~~'0xcafebabe'; // -889275714
~~'deadbeef';   // 0

Number函數(shù)將字符串轉(zhuǎn)為數(shù)值的規(guī)則,參見《數(shù)據(jù)的類型轉(zhuǎn)換》一節(jié)。否運(yùn)算對(duì)特殊數(shù)值的處理是:超出32位的整數(shù)將會(huì)被截去超出的位數(shù),NaN和Infinity轉(zhuǎn)為0。

異或運(yùn)算

“異或運(yùn)算”在兩個(gè)二進(jìn)制位不同時(shí)返回1,相同時(shí)返回0。

0^3 // 3

上面表達(dá)式中,0的二進(jìn)制形式是00,3的二進(jìn)制形式是11,它們每一個(gè)二進(jìn)制位都不同,所以得到11(即3)。

“異或運(yùn)算”有一個(gè)特殊運(yùn)用,連續(xù)對(duì)兩個(gè)數(shù)a和b進(jìn)行三次異或運(yùn)算,a?=b, b?=a, a?=b,可以互換它們的值(詳見維基百科)。這意味著,使用“異或運(yùn)算”可以在不引入臨時(shí)變量的前提下,互換兩個(gè)變量的值。

var a = 10;
var b = 99;

a^=b, b^=a, a^=b;

a // 99
b // 10

這是互換兩個(gè)變量的值的最快方法。

異或運(yùn)算也可以用來取整。

12.9^0 // 12

左移運(yùn)算符(<<)

左移運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制形式向前移動(dòng),尾部補(bǔ)0。

4 << 1
// 8
// 因?yàn)?的二進(jìn)制形式為100,左移一位為1000(即十進(jìn)制的8)

-4 << 1
// -8

上面代碼中,-4左移一位之所以得到-8,是因?yàn)?4的二進(jìn)制形式是11111111111111111111111111111100,左移一位后得到11111111111111111111111111111000,該數(shù)轉(zhuǎn)為十進(jìn)制(減去1后取反,再加上負(fù)號(hào))即為-8。

如果左移0位,就相當(dāng)于取整,對(duì)于正數(shù)和負(fù)數(shù)都有效。

13.5 << 0
// 13

-13.5 << 0
// -13

左移運(yùn)算符用于二進(jìn)制數(shù)值非常方便。

var color = {r: 186, g: 218, b: 85};

// RGB to HEX
var rgb2hex = function(r, g, b) {
    return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).substr(1);
}

rgb2hex(color.r,color.g,color.b)
// "#bada55"

上面代碼使用左移運(yùn)算符,將顏色的RGB值轉(zhuǎn)為HEX值。

右移運(yùn)算符(>>)

右移運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制形式向右移動(dòng),頭部補(bǔ)上最左位的值,即整數(shù)補(bǔ)0,負(fù)數(shù)補(bǔ)1。

4 >> 1
// 2
/* 
// 因?yàn)?的二進(jìn)制形式為00000000000000000000000000000100,
// 右移一位得到00000000000000000000000000000010,
// 即為十進(jìn)制的2
*/

-4 >> 1
// -2
/*
// 因?yàn)?4的二進(jìn)制形式為11111111111111111111111111111100,
// 右移一位,頭部補(bǔ)1,得到11111111111111111111111111111110,
// 即為十進(jìn)制的-2
*/

右移運(yùn)算可以模擬2的整除運(yùn)算。

5 >> 1 
// 相當(dāng)于 5 / 2 = 2

21 >> 2 
// 相當(dāng)于 21 / 4 = 5

21 >> 3 
// 相當(dāng)于 21 / 8 = 2

21 >> 4 
// 相當(dāng)于 21 / 16 = 1

帶符號(hào)位的右移運(yùn)算符(>>>)

該運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制形式向右移動(dòng),不管正數(shù)或負(fù)數(shù),頭部一律補(bǔ)0。所以,該運(yùn)算總是得到正值,這就是它的名稱“帶符號(hào)位的右移”的涵義。對(duì)于正數(shù),該運(yùn)算的結(jié)果與右移運(yùn)算符(>>)完全一致,區(qū)別主要在于負(fù)數(shù)。

4 >>> 1
// 2

-4 >>> 1
// 2147483646
/*
// 因?yàn)?4的二進(jìn)制形式為11111111111111111111111111111100,
// 帶符號(hào)位的右移一位,得到01111111111111111111111111111110,
// 即為十進(jìn)制的2147483646。
*/

開關(guān)作用

位運(yùn)算符可以用作設(shè)置對(duì)象屬性的開關(guān)。

假定某個(gè)對(duì)象有四個(gè)開關(guān),每個(gè)開關(guān)都是一個(gè)變量,取值為2的整數(shù)次冪。

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000

上面代碼設(shè)置A、B、C、D四個(gè)開關(guān),每個(gè)開關(guān)分別占有1個(gè)二進(jìn)制位。

現(xiàn)在假設(shè)需要打開ABD三個(gè)開關(guān),我們可以構(gòu)造一個(gè)掩碼變量。

var mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011

上面代碼對(duì)ABD三個(gè)變量進(jìn)行“或運(yùn)算”,得到掩碼值為二進(jìn)制的1011。

有了掩碼,就可以用“與運(yùn)算”檢驗(yàn)當(dāng)前設(shè)置是否與開關(guān)設(shè)置一致。

if (flags & FLAG_C) { // 0101 & 0100 => 0100 => true
   // ...
}

上面代碼表示,如果當(dāng)前設(shè)置與掩碼一致,則返回true,否則返回false。

“或運(yùn)算”可以將當(dāng)前設(shè)置改成開關(guān)設(shè)置。

flags |= mask;

“與運(yùn)算”可以將當(dāng)前設(shè)置中凡是與開關(guān)設(shè)置不一樣的項(xiàng),全部關(guān)閉。

flags &= mask;

“異或運(yùn)算”可以切換(toggle)當(dāng)前設(shè)置。

flags = flags ^ mask;

“否運(yùn)算”可以翻轉(zhuǎn)當(dāng)前設(shè)置。

flags = ~flags;

其他運(yùn)算符

圓括號(hào)運(yùn)算符

在JavaScript中,圓括號(hào)是一種運(yùn)算符,它有兩種用法:如果把表達(dá)式放在圓括號(hào)之中,作用是求值;如果跟在函數(shù)的后面,作用是調(diào)用函數(shù)。

把表達(dá)式放在圓括號(hào)之中,將返回表達(dá)式的值。

(1) // 1
('a') // a
(1+2) // 3

把對(duì)象放在圓括號(hào)之中,則會(huì)返回對(duì)象的值,即對(duì)象本身。

var o = {p:1};

(o)
// Object {p: 1}

將函數(shù)放在圓括號(hào)中,會(huì)返回函數(shù)本身。如果圓括號(hào)緊跟在函數(shù)的后面,就表示調(diào)用函數(shù),即對(duì)函數(shù)求值。

function f(){return 1;}

(f) // function f(){return 1;}
f() // 1

上面的代碼先定義了一個(gè)函數(shù),然后依次將函數(shù)放在圓括號(hào)之中、將圓括號(hào)跟在函數(shù)后面,得到的結(jié)果是不一樣的。

由于圓括號(hào)的作用是求值,如果將語句放在圓括號(hào)之中,就會(huì)報(bào)錯(cuò),因?yàn)檎Z句沒有返回值。

(var a =1)
// SyntaxError: Unexpected token var

void運(yùn)算符

void運(yùn)算符的作用是執(zhí)行一個(gè)表達(dá)式,然后返回undefined。

void 0 // undefined
void (0) // undefined

上面是void運(yùn)算符的兩種寫法,都正確。建議采用后一種形式,即總是使用括號(hào)。因?yàn)関oid運(yùn)算符的優(yōu)先性很高,如果不使用括號(hào),容易造成錯(cuò)誤的結(jié)果。比如,void 4+7 實(shí)際上等同于 (void 4) +7 。

下面是void運(yùn)算符的一個(gè)例子。

var x = 3;
void (x = 5) //undefined
x // 5

這個(gè)運(yùn)算符主要是用于書簽工具(bookmarklet)或者用于在超級(jí)鏈接中插入代碼,目的是返回undefined可以防止網(wǎng)頁(yè)跳轉(zhuǎn)。

javascript:void window.open("http://example.com/")

比如,下面是常用于網(wǎng)頁(yè)鏈接的觸發(fā)鼠標(biāo)點(diǎn)擊事件的寫法。

<a href="#" onclick="f();">文字</a>

上面代碼有一個(gè)問題,函數(shù)f必須返回false,或者onclick事件必須返回false,否則會(huì)引起瀏覽器跳轉(zhuǎn)到另一個(gè)頁(yè)面。

function f(){
    // some code
    return false;
}

或者寫成

<a href="#" onclick="f();return false;">文字</a>

void運(yùn)算符可以取代上面兩種寫法。

<a href="javascript:void(0)" onclick="f();">文字</a>

逗號(hào)運(yùn)算符

逗號(hào)運(yùn)算符用于對(duì)兩個(gè)表達(dá)式求值,并返回后一個(gè)表達(dá)式的值。

"a", "b" // "b"

var x = 0;
var y = (x++, 10);
x // 1
y // 10

運(yùn)算順序

(1)運(yùn)算符的優(yōu)先級(jí)

JavaScript各種運(yùn)算符的優(yōu)先級(jí)別(Operator Precedence)是不一樣的。優(yōu)先級(jí)高的運(yùn)算符先執(zhí)行,優(yōu)先級(jí)低的運(yùn)算符后執(zhí)行。

4+5*6 // 34

上面的代碼中,乘法運(yùn)算符(*)的優(yōu)先性高于加法運(yùn)算符(+),所以先執(zhí)行乘法,再執(zhí)行加法,相當(dāng)于下面這樣。

4+(5*6) // 34

(2)圓括號(hào)的作用

圓括號(hào)可以用來提高運(yùn)算的優(yōu)先級(jí),即圓括號(hào)中的運(yùn)算符會(huì)第一個(gè)運(yùn)算。

(4+5)*6 // 54

上面代碼中,由于使用了圓括號(hào),加法會(huì)先于乘法執(zhí)行。

由于運(yùn)算符的優(yōu)先級(jí)別十分繁雜,且都是來自硬性規(guī)定,所以本書不打算列出具體的規(guī)則,只是建議讀者總是使用圓括號(hào),保證運(yùn)算順序清晰可讀,這對(duì)代碼的維護(hù)和除錯(cuò)至關(guān)重要。

5 + 2 * 4 / 2 % 3 + 10 - 3 

total = 5;
total *= 2 + 3

上面代碼的運(yùn)算順序,就不容易一眼看出來,容易導(dǎo)致錯(cuò)誤,應(yīng)該加上圓括號(hào)幫助閱讀。

(3)左結(jié)合與右結(jié)合

對(duì)于優(yōu)先級(jí)別相同的運(yùn)算符,大多數(shù)情況,計(jì)算順序總是從左到右,這叫做運(yùn)算符的“左結(jié)合”(left-to-right associativity),即從左邊開始計(jì)算。

x + y + z

上面代碼先計(jì)算最左邊的x與y的和,然后再計(jì)算與z的和。

但是少數(shù)運(yùn)算符的計(jì)算順序是從右到左,即從右邊開始計(jì)算,這叫做運(yùn)算符的“右結(jié)合”(right-to-left associativity)。其中,最主要的是賦值運(yùn)算符(=)和三元條件運(yùn)算符(?:)。

w = x = y = z;
q = a?b:c?d:e?f:g;

上面代碼的運(yùn)算結(jié)果,相當(dāng)于下面的樣子。

w = (x = (y = z)); 
q = a?b:(c?d:(e?f:g));

參考鏈接

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)