W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
運(yùn)算符是處理數(shù)據(jù)的基本方法,用來(lái)從現(xiàn)有數(shù)據(jù)得到新的數(shù)據(jù)。JavaScript與其他編程語(yǔ)言一樣,提供了多種運(yùn)算符。本節(jié)逐一介紹這些運(yùn)算符。
加法運(yùn)算符(+
)是最常見(jiàn)的運(yùn)算符之一,但是使用規(guī)則卻相對(duì)復(fù)雜。因?yàn)樵贘avaScript語(yǔ)言里面,這個(gè)運(yùn)算符可以完成兩種運(yùn)算,既可以處理算術(shù)的加法,也可以用作字符串連接,它們都寫成+
。
// 加法
1 + 1 // 2
true + true // 2
1 + true // 2
// 字符串連接
'1' + '1' // "11"
'1.1' + '1.1' // "1.11.1"
它的算法步驟如下。
valueOf
方法,如果結(jié)果還不是原始類型的值,再執(zhí)行toString
方法;如果對(duì)象是Date
實(shí)例,則先執(zhí)行toString
方法)。下面是一些例子。
'1' + {foo: 'bar'} // "1[object Object]"
'1' + 1 // "11"
'1' + true // "1true"
'1' + [1] // "11"
上面代碼中,由于運(yùn)算符左邊是一個(gè)字符串,導(dǎo)致右邊的運(yùn)算子都會(huì)先轉(zhuǎn)為字符串,然后執(zhí)行字符串連接運(yùn)算。
這種由于參數(shù)不同,而改變自身行為的現(xiàn)象,叫做“重載”(overload)。由于加法運(yùn)算符是運(yùn)行時(shí)決定到底執(zhí)行那種運(yùn)算,使用的時(shí)候必須很小心。
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
上面代碼中,運(yùn)算結(jié)果由于字符串的位置不同而不同。
下面的寫法,可以用來(lái)將一個(gè)值轉(zhuǎn)為字符串。
x + ''
上面代碼中,一個(gè)值加上空字符串,會(huì)使得該值轉(zhuǎn)為字符串形式。
加法運(yùn)算符會(huì)將其他類型的值,自動(dòng)轉(zhuǎn)為字符串,然后再執(zhí)行連接運(yùn)算。
[1, 2] + [3]
// "1,23"
// 等同于
String([1, 2]) + String([3])
// '1,2' + '3'
上面代碼中,兩個(gè)數(shù)組相加,會(huì)先轉(zhuǎn)成字符串,然后再連接。這種數(shù)據(jù)類型的自動(dòng)轉(zhuǎn)換,參見(jiàn)《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。
加法運(yùn)算符一定有左右兩個(gè)運(yùn)算子,如果只有右邊一個(gè)運(yùn)算子,就是另一個(gè)運(yùn)算符,叫做“數(shù)值運(yùn)算符”。
+ - 3 // 等同于 +(-3)
+ 1 + 2 // 等同于 +(1 + 2)
+ '1' // 1
上面代碼中,數(shù)值運(yùn)算符用于返回右邊運(yùn)算子的數(shù)值形式,詳細(xì)解釋見(jiàn)下文。
你可能會(huì)問(wèn),如果只有左邊一個(gè)運(yùn)算子,會(huì)出現(xiàn)什么情況?答案是會(huì)報(bào)錯(cuò)。
1 +
// SyntaxError: Unexpected end of input
加法運(yùn)算符以外的其他算術(shù)運(yùn)算符(比如減法、除法和乘法),都不會(huì)發(fā)生重載。它們的規(guī)則是:所有運(yùn)算子一律轉(zhuǎn)為數(shù)值,再進(jìn)行相應(yīng)的數(shù)學(xué)運(yùn)算。
1 - '2' // -1
1 * '2' // 2
1 / '2' // 0.5
上面代碼中,減法、除法和乘法運(yùn)算符,都是將字符串自動(dòng)轉(zhuǎn)為數(shù)值,然后再運(yùn)算。
由于加法運(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í),得到的是一個(gè)字符串;減法運(yùn)算時(shí),得到卻是一個(gè)數(shù)值。
JavaScript提供9個(gè)算術(shù)運(yùn)算符,用來(lái)完成基本的算術(shù)運(yùn)算。
x + y
x - y
x * y
x / y
x % y
++x
或者 x++
--x
或者 x--
+x
-x
減法、乘法、除法運(yùn)算法比較單純,就是執(zhí)行相應(yīng)的數(shù)學(xué)運(yùn)算。下面介紹其他幾個(gè)算術(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ù)不是精確的值,無(wú)法得到完全準(zhǔn)確的結(jié)果。
6.5 % 2.1
// 0.19999999999999973
自增和自減運(yùn)算符,是一元運(yùn)算符,只需要一個(gè)運(yùn)算子。它們的作用是將運(yùn)算子首先轉(zhuǎn)為數(shù)值,然后加上1或者減去1。它們會(huì)修改原始變量。
var x = 1;
++x // 2
x // 2
--x // 1
x // 1
上面代碼的變量x
自增后,返回2
,再進(jìn)行自減,返回1
。這兩種情況都會(huì)使得,原始變量x
的值發(fā)生改變。
自增和自減運(yùn)算符有一個(gè)需要注意的地方,就是放在變量之后,會(huì)先返回變量操作前的值,再進(jìn)行自增/自減操作;放在變量之前,會(huì)先進(jìn)行自增/自減操作,再返回變量操作后的值。
var x = 1;
var y = 1;
x++ // 1
++y // 2
上面代碼中,x
是先返回當(dāng)前值,然后自增,所以得到1
;y
是先自增,然后返回新的值,所以得到2
。
數(shù)值運(yùn)算符(+
)同樣使用加號(hào),但是加法運(yùn)算符是二元運(yùn)算符(需要兩個(gè)操作數(shù)),它是一元運(yùn)算符(只需要一個(gè)操作數(shù))。
數(shù)值運(yùn)算符的作用在于可以將任何值轉(zhuǎn)為數(shù)值(與Number
函數(shù)的作用相同)。
+true // 1
+[] // 0
+{} // NaN
上面代碼表示,非數(shù)值類型的值經(jīng)過(guò)數(shù)值運(yùn)算符以后,都變成了數(shù)值(最后一行NaN
也是數(shù)值)。具體的類型轉(zhuǎn)換規(guī)則,參見(jiàn)《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。
負(fù)數(shù)值運(yùn)算符(-
),也同樣具有將一個(gè)值轉(zhuǎn)為數(shù)值的功能,只不過(guò)得到的值正負(fù)相反。連用兩個(gè)負(fù)數(shù)值運(yùn)算符,等同于數(shù)值運(yùn)算符。
var x = 1;
-x // -1
-(-x) // 1
上面代碼最后一行的圓括號(hào)不可少,否則會(huì)變成遞減運(yùn)算符。
數(shù)值運(yùn)算符號(hào)和負(fù)數(shù)值運(yùn)算符,都會(huì)返回一個(gè)新的值,而不會(huì)改變?cè)甲兞康闹怠?/p>
賦值運(yùn)算符(Assignment Operators)用于給變量賦值。
最常見(jiàn)的賦值運(yùn)算符,當(dāng)然就是等號(hào)(=
),表達(dá)式x = y
表示將y
的值賦給x
。
除此之外,JavaScript還提供其他11個(gè)復(fù)合的賦值運(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
這些復(fù)合的賦值運(yùn)算符,都是先進(jìn)行指定運(yùn)算,然后將得到值返回給左邊的變量。
比較運(yùn)算符用于比較兩個(gè)值,然后返回一個(gè)布爾值,表示是否滿足比較條件。
2 > 1 // true
上面代碼比較2
是否大于1
,返回true
。
JavaScript一共提供了8個(gè)比較運(yùn)算符。
==
相等===
嚴(yán)格相等!=
不相等!==
嚴(yán)格不相等<
小于<=
小于或等于>
大于>=
大于或等于比較運(yùn)算符可以比較各種類型的值,不僅僅是數(shù)值。
除了相等運(yùn)算符號(hào)和精確相等運(yùn)算符,其他比較運(yùn)算符的算法如下。
Number
函數(shù))。下面的例子是兩個(gè)原始類型的值之間的比較。
5 > '4' // true
// 等同于 5 > Number('4')
// 即 5 > 4
true > false // true
// 等同于 Number(true) > Number(false)
// 即 1 > 0
2 > true // true
// 等同于 2 > Number(true)
// 即 2 > 1
上面代碼中,字符串和布爾值都會(huì)先轉(zhuǎn)成數(shù)值,再進(jìn)行比較。
如果運(yùn)算子是對(duì)象,必須先將其轉(zhuǎn)為原始類型的值,即先調(diào)用valueOf
方法,如果返回的還是對(duì)象,再接著調(diào)用toString
方法。
var x = [2];
x > '11' // true
// 等同于 [2].valueOf().toString() > '11'
// 即 '2' > '11'
x.valueOf = function () { return '1' };
x > '11' // false
// 等同于 [2].valueOf() > '11'
// 即 '1' > '11'
兩個(gè)對(duì)象之間的比較也是如此。
[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'
[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'
{x: 2} >= {x: 1} // true
// 等同于 {x: 2}.valueOf().toString() >= {x: 1}.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'
字符串按照字典順序進(jìn)行比較。
'cat' > 'dog' // false
'cat' > 'catalog' // false
JavaScript 引擎內(nèi)部首先比較首字符的 Unicode 碼點(diǎn),如果相等,再比較第二個(gè)字符的 Unicode 碼點(diǎn),以此類推。
'cat' > 'Cat' // true'
上面代碼中,小寫的c
的 Unicode 碼點(diǎn)(99
)大于大寫的C
的 Unicode 碼點(diǎn)(67
),所以返回true
。
由于所有字符都有 Unicode 碼點(diǎn),因此漢字也可以比較。
'大' > '小' // false
上面代碼中,“大”的 Unicode 碼點(diǎn)是22823,“小”是23567,因此返回false
。
JavaScript提供兩個(gè)相等運(yùn)算符:==
和===
。
簡(jiǎn)單說(shuō),它們的區(qū)別是相等運(yùn)算符(==
)比較兩個(gè)值是否相等,嚴(yán)格相等運(yùn)算符(===
)比較它們是否為“同一個(gè)值”。如果兩個(gè)值不是同一類型,嚴(yán)格相等運(yùn)算符(===
)直接返回false
,而相等運(yùn)算符(==
)會(huì)將它們轉(zhuǎn)化成同一個(gè)類型,再用嚴(yán)格相等運(yùn)算符進(jìn)行比較。
嚴(yán)格相等運(yùn)算符的算法如下。
(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)轭愋秃椭刀枷嗤?,返?code class="highlighter-rouge">true。
需要注意的是,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)算比較的是,它們是否引用同一個(gè)內(nèi)存地址,而運(yùn)算符兩邊的空對(duì)象、空數(shù)組、空函數(shù)的值,都存放在不同的內(nèi)存地址,結(jié)果當(dāng)然是false
。
如果兩個(gè)變量引用同一個(gè)對(duì)象,則它們相等。
var v1 = {};
var v2 = v1;
v1 === v2 // true
注意,對(duì)于兩個(gè)對(duì)象的比較,嚴(yán)格相等運(yùn)算符比較的是地址,而大于或小于運(yùn)算符比較的是值。
new Date() > new Date() // false
new Date() < new Date() // false
new Date() === new Date() // false
上面的三個(gè)表達(dá)式,前兩個(gè)比較的是值,最后一個(gè)比較的是地址,所以都返回false
。
(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)算符比較相同類型的數(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
// 等同于 1 === 1
0 == false // true
// 等同于 0 === 0
2 == true // false
// 等同于 2 === 1
2 == false // false
// 等同于 2 === 0
'true' == true // false
// 等同于 Number('true') === Number(true)
// 等同于 NaN === 1
'' == 0 // true
// 等同于 Number('') === 0
// 等同于 0 === 0
'' == false // true
// 等同于 Number('') === Number(false)
// 等同于 0 === 0
'1' == true // true
// 等同于 Number('1') === Number(true)
// 等同于 1 === 1
'\n 123 \t' == 123 // true
// 因?yàn)樽址D(zhuǎn)為數(shù)字時(shí),省略前置和后置的空格
上面代碼將字符串和布爾值都轉(zhuǎn)為數(shù)值,然后再進(jìn)行比較。具體的字符串與布爾值的類型轉(zhuǎn)換規(guī)則,參見(jiàn)《數(shù)據(jù)類型轉(zhuǎn)換》章節(jié)。
(2)對(duì)象與原始類型值比較
對(duì)象(這里指廣義的對(duì)象,包括數(shù)組和函數(shù))與原始類型的值比較時(shí),對(duì)象轉(zhuǎn)化成原始類型的值,再進(jìn)行比較。
[1] == 1 // true
// 等同于 Number([1]) == 1
[1] == '1' // true
// 等同于 String([1]) == Number('1')
[1] == true // true
// 等同于 Number([1]) == Number(true)
上面代碼中,數(shù)組[1]
分別與數(shù)值、字符串和布爾值進(jìn)行比較,會(huì)先轉(zhuǎn)成字符串或數(shù)值,再進(jìn)行比較。比如,與數(shù)值1
比較時(shí),數(shù)組[1]
會(huì)被自動(dòng)轉(zhuǎn)換成數(shù)值1
,因此得到true
。具體的對(duì)象類型轉(zhuǎn)換規(guī)則,參見(jiàn)《數(shù)據(jù)類型轉(zhuǎn)換》章節(jié)。
(3)undefined和null
undefined
和null
與其他類型的值比較時(shí),結(jié)果都為false
,它們互相比較時(shí)結(jié)果為true
。
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
undefined == null // true
絕大多數(shù)情況下,對(duì)象與undefined
與null
比較,都返回false
。只有在對(duì)象轉(zhuǎn)為原始值得到undefined
時(shí),才會(huì)返回true
,這種情況是非常罕見(jiàn)的。
(4)相等運(yùn)算符的缺點(diǎn)
相等運(yùn)算符隱藏的類型轉(zhuǎn)換,會(huì)帶來(lái)一些違反直覺(jué)的結(jié)果。
'' == '0' // false
0 == '' // true
0 == '0' // true
2 == true // false
2 == false // false
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)算符用于將表達(dá)式轉(zhuǎn)為布爾值,一共包含四個(gè)運(yùn)算符。
!
&&
||
?:
取反運(yùn)算符形式上是一個(gè)感嘆號(hào),用于將布爾值變?yōu)橄喾粗担?code class="highlighter-rouge">true變成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)過(guò)取反運(yùn)算后,都變成了布爾值。
如果對(duì)一個(gè)值連續(xù)做兩次取反運(yùn)算,等于將其轉(zhuǎn)為對(duì)應(yīng)的布爾值,與Boolean
函數(shù)的作用相同。這是一種常用的類型轉(zhuǎn)換的寫法。
!!x
// 等同于
Boolean(x)
上面代碼中,不管x
是什么類型的值,經(jīng)過(guò)兩次取反運(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)算規(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
的值沒(méi)變。
這種跳過(guò)第二個(gè)運(yùn)算子的機(jī)制,被稱為“短路”。有些程序員喜歡用它取代if
結(jié)構(gòu),比如下面是一段if
結(jié)構(gòu)的代碼,就可以用且運(yùn)算符改寫。
if (i) {
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)算規(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í),沒(méi)有提供參數(shù),則該參數(shù)默認(rèn)設(shè)置為空字符串。
三元條件運(yùn)算符用問(wèn)號(hào)(?)和冒號(hào)(:),分隔三個(gè)表達(dá)式。如果第一個(gè)表達(dá)式的布爾值為true
,則返回第二個(gè)表達(dá)式的值,否則返回第三個(gè)表達(dá)式的值。
't' ? 'hello' : 'world' // "hello"
0 ? 'hello' : 'world' // "world"
上面代碼的t
和0
的布爾值分別為true
和false
,所以分別返回第二個(gè)和第三個(gè)表達(dá)式的值。
通常來(lái)說(shuō),三元條件表達(dá)式與if...else
語(yǔ)句具有同樣表達(dá)效果,前者可以表達(dá)的,后者也能表達(dá)。但是兩者具有一個(gè)重大差別,if...else
是語(yǔ)句,沒(méi)有返回值;三元條件表達(dá)式是表達(dá)式,具有返回值。所以,在需要返回值的場(chǎng)合,只能使用三元條件表達(dá)式,而不能使用if..else
。
console.log(true ? 'T' : 'F');
上面代碼中,console.log
方法的參數(shù)必須是一個(gè)表達(dá)式,這時(shí)就只能使用三元條件表達(dá)式。如果要用if...else
語(yǔ)句,就必須改變整個(gè)代碼寫法了。
位運(yùn)算符用于直接對(duì)二進(jìn)制位進(jìn)行計(jì)算,一共有7個(gè)。
或運(yùn)算(or):符號(hào)為|
,表示若兩個(gè)二進(jìn)制位都為0,則結(jié)果為0,否則為1。
與運(yùn)算(and):符號(hào)為&
,表示若兩個(gè)二進(jìn)制位都為1,則結(jié)果為1,否則為0。
否運(yùn)算(not):符號(hào)為~
,表示對(duì)一個(gè)二進(jìn)制位取反。
異或運(yùn)算(xor):符號(hào)為^
,表示若兩個(gè)二進(jìn)制位不相同,則結(jié)果為1,否則為0。
左移運(yùn)算(left shift):符號(hào)為<<
,詳見(jiàn)下文解釋。
右移運(yùn)算(right shift):符號(hào)為>>
,詳見(jiàn)下文解釋。
帶符號(hào)位的右移運(yùn)算(zero filled right shift):符號(hào)為>>>
,詳見(jiàn)下文解釋。
這些位運(yùn)算符直接處理每一個(gè)比特位(bit),所以是非常底層的運(yùn)算,好處是速度極快,缺點(diǎn)是很不直觀,許多場(chǎng)合不能使用它們,否則會(huì)使代碼難以理解和查錯(cuò)。
有一點(diǎn)需要特別注意,位運(yùn)算符只對(duì)整數(shù)起作用,如果一個(gè)運(yùn)算子不是整數(shù),會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再執(zhí)行。另外,雖然在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
(不管是整數(shù)或小數(shù))轉(zhuǎn)為32位整數(shù)。
利用這個(gè)特性,可以寫出一個(gè)函數(shù),將任意數(shù)值轉(zhuǎn)為32位整數(shù)。
function toInt32(x) {
return x | 0;
}
toInt32(1.001) // 1
toInt32(1.999) // 1
toInt32(1) // 1
toInt32(-1) // -1
toInt32(Math.pow(2, 32) + 1) // 1
toInt32(Math.pow(2, 32) - 1) // -1
上面代碼中,最后兩行得到1
和-1
,是因?yàn)橐粋€(gè)整數(shù)大于32位的數(shù)位都會(huì)被舍去。
這兩種運(yùn)算比較容易理解,就是逐位比較兩個(gè)運(yùn)算子?!盎蜻\(yùn)算”的規(guī)則是,兩個(gè)二進(jìn)制位之中只要有一個(gè)為1,就返回1,否則返回0?!芭c運(yùn)算”的規(guī)則是,兩個(gè)二進(jìn)制位之中只要有一個(gè)位為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
需要注意的是,這種取整方法不適用超過(guò)32位整數(shù)最大值2147483647的數(shù)。
2147483649.4 | 0;
// -2147483647
“否運(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)部采用補(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
??紤]到這樣的過(guò)程比較麻煩,可以簡(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
~~47.11 // 47
~~1.9999 // 1
~~3 // 3
使用否運(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ī)則,參見(jiàn)《數(shù)據(jù)的類型轉(zhuǎn)換》一節(jié)。否運(yùn)算對(duì)特殊數(shù)值的處理是:超出32位的整數(shù)將會(huì)被截去超出的位數(shù),NaN和Infinity轉(zhuǎn)為0。
對(duì)于其他類型的參數(shù),否運(yùn)算也是先用Number
轉(zhuǎn)為數(shù)值,然后再進(jìn)行處理。
~~[] // 0
~~NaN // 0
~~null // 0
“異或運(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,可以互換它們的值(詳見(jiàn)維基百科)。這意味著,使用“異或運(yùn)算”可以在不引入臨時(shí)變量的前提下,互換兩個(gè)變量的值。
var a = 10;
var b = 99;
a ^= b, b ^= a, a ^= b;
a // 99
b // 10
這是互換兩個(gè)變量的值的最快方法。
異或運(yùn)算也可以用來(lái)取整。
12.9 ^ 0 // 12
左移運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制值向左移動(dòng)指定的位數(shù),尾部補(bǔ)0,即乘以2的指定次方(最高位即符號(hào)位不參與移動(dòng))。
// 4 的二進(jìn)制形式為100,
// 左移一位為1000(即十進(jìn)制的8)
// 相當(dāng)于乘以2的1次方
4 << 1
// 8
-4 << 1
// -8
上面代碼中,-4
左移一位得到-8
,是因?yàn)?code class="highlighter-rouge">-4的二進(jìn)制形式是11111111111111111111111111111100
,左移一位后得到11111111111111111111111111111000
,該數(shù)轉(zhuǎn)為十進(jìn)制(減去1后取反,再加上負(fù)號(hào))即為-8
。
如果左移0位,就相當(dāng)于將該數(shù)值轉(zhuǎn)為32位整數(shù),等同于取整,對(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
// (1 << 24)的作用為保證結(jié)果是6位數(shù)
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)算符表示將一個(gè)數(shù)的二進(jìn)制值向右移動(dòng)指定的位數(shù),頭部補(bǔ)0,即除以2的指定次方(最高位即符號(hào)位不參與移動(dòng))。
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
該運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制形式向右移動(dòng),包括符號(hào)位也參與移動(dòng),頭部補(bǔ)0。所以,該運(yùn)算總是得到正值。對(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。
*/
這個(gè)運(yùn)算實(shí)際上將一個(gè)值轉(zhuǎn)為32位無(wú)符號(hào)整數(shù)。
查看一個(gè)負(fù)整數(shù)在計(jì)算機(jī)內(nèi)部的儲(chǔ)存形式,最快的方法就是使用這個(gè)運(yùn)算符。
-1 >>> 0 // 4294967295
上面代碼表示,-1
作為32位整數(shù)時(shí),內(nèi)部的儲(chǔ)存形式使用無(wú)符號(hào)整數(shù)格式解讀,值為 4294967295(即(2^32)-1
,等于11111111111111111111111111111111
)。
位運(yùn)算符可以用作設(shè)置對(duì)象屬性的開(kāi)關(guān)。
假定某個(gè)對(duì)象有四個(gè)開(kāi)關(guān),每個(gè)開(kāi)關(guān)都是一個(gè)變量。那么,可以設(shè)置一個(gè)四位的二進(jìn)制數(shù),它的每個(gè)位對(duì)應(yīng)一個(gè)開(kāi)關(guān)。
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è)開(kāi)關(guān),每個(gè)開(kāi)關(guān)分別占有一個(gè)二進(jìn)制位。
然后,就可以用“與運(yùn)算”檢驗(yàn),當(dāng)前設(shè)置是否打開(kāi)了指定開(kāi)關(guān)。
var flags = 5; // 二進(jìn)制的0101
if (flags & FLAG_C) {
// ...
}
// 0101 & 0100 => 0100 => true
上面代碼檢驗(yàn)是否打開(kāi)了開(kāi)關(guān)C
。如果打開(kāi),會(huì)返回true
,否則返回false
。
現(xiàn)在假設(shè)需要打開(kāi)A
B
D
三個(gè)開(kāi)關(guān),我們可以構(gòu)造一個(gè)掩碼變量。
var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011
上面代碼對(duì)A
B
D
三個(gè)變量進(jìn)行“或運(yùn)算”,得到掩碼值為二進(jìn)制的1011
。
有了掩碼,“或運(yùn)算”可以確保打開(kāi)指定的開(kāi)關(guān)。
flags = flags | mask;
“與運(yùn)算”可以將當(dāng)前設(shè)置中凡是與開(kāi)關(guān)設(shè)置不一樣的項(xiàng),全部關(guān)閉。
flags = flags & mask;
“異或運(yùn)算”可以切換(toggle)當(dāng)前設(shè)置,即第一次執(zhí)行可以得到當(dāng)前設(shè)置的相反值,再執(zhí)行一次又得到原來(lái)的值。
flags = flags ^ mask;
“否運(yùn)算”可以翻轉(zhuǎn)當(dāng)前設(shè)置,即原設(shè)置為0
,運(yùn)算后變?yōu)?code class="highlighter-rouge">1;原設(shè)置為1
,運(yùn)算后變?yōu)?code class="highlighter-rouge">0。
flags = ~flags;
void
運(yùn)算符的作用是執(zhí)行一個(gè)表達(dá)式,然后不返回任何值,或者說(shuō)返回undefined
。
void 0 // undefined
void(0) // undefined
上面是void
運(yùn)算符的兩種寫法,都正確。建議采用后一種形式,即總是使用括號(hào)。因?yàn)?code class="highlighter-rouge">void運(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)。
<a href="javascript:void window.open('http://example.com/')">
點(diǎn)擊打開(kāi)新窗口
</a>
上面代碼用于在網(wǎng)頁(yè)中創(chuàng)建一個(gè)鏈接,點(diǎn)擊后會(huì)打開(kāi)一個(gè)新窗口。如果沒(méi)有void
,點(diǎn)擊后就會(huì)在當(dāng)前窗口打開(kāi)鏈接。
下面是常見(jiàn)的網(wǎng)頁(yè)中觸發(fā)鼠標(biāo)點(diǎn)擊事件的寫法。
<a href="http://example.com" onclick="f();">文字</a>
上面代碼有一個(gè)問(wèn)題,函數(shù)f
必須返回false
,或者說(shuō)onclick
事件必須返回false
,否則會(huì)引起瀏覽器跳轉(zhuǎn)到example.com
。
function f() {
// some code
return false;
}
或者寫成
<a href="http://example.com" onclick="f();return false;">文字</a>
void
運(yùn)算符可以取代上面兩種寫法。
<a href="javascript: void(f())">文字</a>
下面的代碼會(huì)提交表單,但是不會(huì)產(chǎn)生頁(yè)面跳轉(zhuǎn)。
<a href="javascript: void(document.form.submit())">
文字</a>
逗號(hào)運(yùn)算符用于對(duì)兩個(gè)表達(dá)式求值,并返回后一個(gè)表達(dá)式的值。
'a', 'b' // "b"
var x = 0;
var y = (x++, 10);
x // 1
y // 10
上面代碼中,逗號(hào)運(yùn)算符返回后一個(gè)表達(dá)式的值。
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
如果多個(gè)運(yùn)算符混寫在一起,常常會(huì)導(dǎo)致令人困惑的代碼。
var x = 1;
var arr = [];
var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];
上面代碼中,變量y
的值就很難看出來(lái),因?yàn)檫@個(gè)表達(dá)式涉及5個(gè)運(yùn)算符,到底誰(shuí)的優(yōu)先級(jí)最高,實(shí)在不容易記住。
根據(jù)語(yǔ)言規(guī)格,這五個(gè)運(yùn)算符的優(yōu)先級(jí)從高到低依次為:小于等于(<=
)、嚴(yán)格相等(===
)、或(||
)、三元(?:
)、等號(hào)(=
)。因此上面的表達(dá)式,實(shí)際的運(yùn)算順序如下。
var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];
記住所有運(yùn)算符的優(yōu)先級(jí),是非常難的,也是沒(méi)有必要的。
圓括號(hào)(()
)可以用來(lái)提高運(yùn)算的優(yōu)先級(jí),因?yàn)樗膬?yōu)先級(jí)是最高的,即圓括號(hào)中的表達(dá)式會(huì)第一個(gè)運(yùn)算。
(4 + 5) * 6 // 54
上面代碼中,由于使用了圓括號(hào),加法會(huì)先于乘法執(zhí)行。
由于運(yùn)算符的優(yōu)先級(jí)別十分繁雜,且都是來(lái)自硬性規(guī)定,因此建議總是使用圓括號(hào),保證運(yùn)算順序清晰可讀,這對(duì)代碼的維護(hù)和除錯(cuò)至關(guān)重要。
順便說(shuō)一下,圓括號(hào)不是運(yùn)算符,而是一種語(yǔ)法結(jié)構(gòu)。它一共有兩種用法:一種是把表達(dá)式放在圓括號(hào)之中,提升運(yùn)算的優(yōu)先級(jí);另一種是跟在函數(shù)的后面,作用是調(diào)用函數(shù)。
注意,因?yàn)閳A括號(hào)不是運(yùn)算符,所以不具有求值作用,只改變運(yùn)算的優(yōu)先級(jí)。
var x = 1;
(x) = 2;
上面代碼的第二行,如果圓括號(hào)具有求值作用,那么就會(huì)變成1 = 2
,這是會(huì)報(bào)錯(cuò)了。但是,上面的代碼可以運(yùn)行,這驗(yàn)證了圓括號(hào)只改變優(yōu)先級(jí),不會(huì)求值。
這也意味著,如果整個(gè)表達(dá)式都放在圓括號(hào)之中,那么不會(huì)有任何效果。
(exprssion)
// 等同于
expression
函數(shù)放在圓括號(hào)中,會(huì)返回函數(shù)本身。如果圓括號(hào)緊跟在函數(shù)的后面,就表示調(diào)用函數(shù)。
function f() {
return 1;
}
(f) // function f(){return 1;}
f() // 1
上面代碼中,函數(shù)放在圓括號(hào)之中會(huì)返回函數(shù)本身,圓括號(hào)跟在函數(shù)后面則是調(diào)用函數(shù)。
圓括號(hào)之中,只能放置表達(dá)式,如果將語(yǔ)句放在圓括號(hào)之中,就會(huì)報(bào)錯(cuò)。
(var a = 1)
// SyntaxError: Unexpected token var
對(duì)于優(yōu)先級(jí)別相同的運(yùn)算符,大多數(shù)情況,計(jì)算順序總是從左到右,這叫做運(yùn)算符的“左結(jié)合”(left-to-right associativity),即從左邊開(kāi)始計(jì)算。
x + y + z
上面代碼先計(jì)算最左邊的x
與y
的和,然后再計(jì)算與z
的和。
但是少數(shù)運(yùn)算符的計(jì)算順序是從右到左,即從右邊開(kāi)始計(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));
上面的兩行代碼,各有三個(gè)等號(hào)運(yùn)算符和三個(gè)三元運(yùn)算符,都是先計(jì)算最右邊的那個(gè)運(yùn)算符。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: