iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 7
6
Modern Web

重新認識 JavaScript系列 第 7

重新認識 JavaScript: Day 07 「比較」與自動轉型的規則

本系列文章已重新編修,並在加入部分 ES6 新篇章後集結成書,有興趣的朋友可至天瓏書局選購,感謝大家支持。

購書連結 https://www.tenlong.com.tw/products/9789864344130

讓我們再次重新認識 JavaScript!


在上一篇文章當中,我們介紹了四則運算的算術運算子 (Arithmetic Operator),那麼在今天的分享當中,我們繼續來看看其他運算子吧。

算術運算子之二

昨天寫完之後才突然想起,算術運算子除了用來做「兩個數值」的四則運算、取餘數的作用以外,還有另外一種是只需要單個數值就可以完成運算,這類運算子通常會被稱為「一元運算子」 (Unary Operator)。


正號 + 與負號 -

如同我們過去在學校所學的數學一樣,正號 + 與負號 - 分別用來表示數字的「正數」與「負數」的狀態。

var a = +10;
var b = -10;

console.log(a);       // 10
console.log(b);       // -10

但有趣的是,若是正號 + 與負號 - 後面帶的並不是一個數字型態的值,那麼 JavaScript 會在背後先透過 Number() 的方法嘗試著將其轉型,再看前面帶的是正號 + 或負號 - 來決定其數值。

var a = "+10";
var b = "-10";
var c = "Hello";

console.log( +a );      // 10
console.log( -a );      // -10
console.log( +b );      // -10
console.log( -b );      // 10
console.log( +c );      // NaN
console.log( -c );      // NaN

而物件型別的情況下,則是會先透過物件的 valueOf() 方法先求得對應的數值,再依照正號 + 或負號 - 來決定其數值。

如果得到 NaN,那麼結果就是 NaN

+true       // 1
+false      // 0
+null       // 0
+function(val){ return val;}      //NaN

所以說,如果你覺得 Number() 太長,又想做數字轉型的話,透過加號 + 也會有一樣的效果。


遞增 ++ 與遞減 --

除了正號 + 與負號 - 之外,另一種更常見的一元運算子就屬「遞增」 ++ 與「遞減」 -- 了。

遞增與遞減就如同字面上意思一樣,當變數遇上了 ++ 就會加一,而 -- 就會減一:

var a = 10;

a++;
console.log(a);       // 11

a--;
console.log(a);       // 10

所以 ++-- 這兩個運算子,你可以把他們當作是: a = a + 1a = a - 1 的意思。

但是除了遞增與遞減的功能外,++-- 這兩個運算子的位置也是有差別的喔。

這裏我們簡單做個比較:

var a = 10;
var b = 10;

a++;
console.log(a);       // 11

++b;
console.log(b);       // 11

我知道看完之後你可能會想翻桌,根本就一樣嘛。
別急,就結果來說, ++ 放在變數前面與放在變數後面,是一樣的沒錯。

我們換個寫法再來一次:

var a = 10;
var b = 10;

console.log(a++);       // 10
console.log(++b);       // 11

console.log(a);         // 11
console.log(b);         // 11

看出差異了嗎?

當我們將 ++ 放在變數後面時,回傳的結果會是「原始的數值」。
++ 放在變數前面時,得到的會是「+1 之後的結果」。

當然事後再將兩者印出時,就都會是 +1 之後的結果了, -- 的概念也是一樣。


結束了算數運算子之後,接著我們要來看比較運算子。

比較運算子 (Comparison Operators)

比較運算子就是用來比較運算子兩側數值 (可能是純值、物件,甚至某個運算式或函數回傳的結果),比較後得到 truefalse

針對不同型別的數值,JavaScript 會嘗試將它們在背景 (自動) 轉型到同樣型態後,再做比較。

「相等」 == 與 「全等」 ===

很多朋友在剛接觸 JavaScript 的時候,應該都會搞不懂「兩個」等號 == 與「三個」等號的差別 ===,這裡就來詳細說明。

一個等號 = 的情況很單純,是「指定、賦值」的意思,像:

var a = 10;

此時會將等號右側的值 (10) 指定至變數 a 當中。

而若要比較數值的情況下,多數程式語言會用「兩個」等號 == ,來為左右兩側的資料進行比較。
當然 JavaScript 也有:

var a = 10;
var b = 100;

console.log( a == b );        // false
console.log( a == 10 );       // true

看起來很 ok 對吧?

但是如果是兩個「資料型態」不同的比較呢?

var a = 10;
var b = "10";

猜猜看,此時 console.log( a == b ); 會是 true 或是 false

答案是 true

這時候問題就來了,一個數字的 10 與字串的 "10" 居然會相等
真不知道該說是 JavaScript 太貼心還是太不嚴謹。

如果是數字與字串也就算了,我們來看看下面這個例子:

true == 'true'      // ?

false == 'false'    // ?

來猜猜看,這兩個運算式的比較結果會是什麼? 答案都是 false

另外我們再來看看各種莫名其妙的比較判斷:

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

[] == []      // false
[] == ![]     // true

[] == ''      // true
[] == 0       // true

[''] == ''    // true
[0] == 0      // true

[0] == ''     // false
[''] == 0     // true

https://ithelp.ithome.com.tw/upload/images/20171210/20065504ivEbEvGmWl.png

然後:

null == undefined   // true

[null] == ''        // true
[null] == 0         // true

[undefined] == ''   // true
[undefined] == 0    // true

看完還覺得 JavaScript 如此貼心嗎? 是噁心吧

https://ithelp.ithome.com.tw/upload/images/20171210/20065504cREVvKMVlw.jpg
「JavaScript 我真是猜不透你啊~」

為什麼會這樣? 等等我們回頭來介紹 JavaScript 自動轉型的規則,你就知道了。


所以後來,為了排除這種奇怪的問題,於是就新增了「三個等號」=== 這個比較運算子。

三個等號 === 與兩個等號 == 雖然都是比較的意思,但最大的差別在於「三個等號 === 不會替數值做自動轉型」。 也就是說,回到一開始的例子:

var a = 10;
var b = "10";

console.log( a == b );      // true

console.log( a === b );     // false

=== 的情況下,數字的 10 與字串的 "10",由於 JavaScript 不會做自動轉型,所以結果會是 false,它只會在雙方的數值與型態都相等的狀況下回傳 true

而原本 null == undefined 的情況下會得到 true,改成 null === undefined 之後,得到的會是正確的 false

這也是為什麼在 JavaScript 這門程式語言中,大家會提倡盡量使用 === 來取代 == 的原因。


不等於 !=!==

除了判斷是否相等以外,與之對應的還有「不等於」。
當然不等於也分成 !=!== 兩個版本: != 的版本會做自動轉型,而 !== 則不會做自動轉型。

就觀念上來說都是一樣的,這裡就不再贅述。


自動轉型的規則

前面提到,在兩個等號 == 的比較運算式下,若是雙方的資料類型不同時,則會進行「自動轉型」,那麼這裡就來說明自動轉型的規則。

  • 如果其中有一個值為「Boolean」的情況下,會將 true 轉型為「數字」的 1false 則會變成數字的 0
  • 如果遇到字串與數字做比較的情況下,則會將字串透過 Number() 嘗試轉型為數字後,再進行比較。
  • 如果其中一方是「物件」型別,而另一方是基本型別的情況下,則會先透過物件的 valueOf() 方法取得對應的基本型別的值,再進行比較。

除此之外,還有前面文章曾提到過的:

  • NaN 不等於 NaN,不管是兩個等號或三個等號都一樣。
  • 當兩個物件進行比較時,要看兩者是否指向同一個「實體」,只有在指向同一個「實體」時才會回傳 true

數值的大於 > 與小於 <

除了判斷是否相等以外,當然也有為數值的「大小」來做比較。

使用的運算子就是大家都很熟悉的大於 >、小於 <、大於等於 >=,以及小於等於 <= 符號。

var a = 10;
var b = 11;
var c = 10;

console.log( a > b );       // false
console.log( b > a );       // true

console.log( a > c );       // false
console.log( a >= c );      // true

當然此類運算子也會遇到不同型別要「自動轉型」的狀況,規則大致如下:

  • 兩者都是數字,則單純就其字面大小比較。
  • 如果其中一個是數字,而另一個不是,則會嘗試在背後將另一個先轉為數字後再比較。
  • 如果兩者都是字串,則會依照字母的順序 (standard lexicographical ordering) 來進行比較。 [註1]
  • 如果其中一者是 boolean 的情況,則會把 true 看成 1false 看成 0 再進行比較。
  • 如果是物件的情況下,則是會先透過物件的 valueOf() 方法先求得對應的數值,若物件沒有 valueOf() 方法的話,則會透過 toString() 轉型再進行比較。

另外, => 不是運算子,是 ES6 的箭頭函式,後續我們會來介紹這個東西,小心不要搞錯了。


  • [註1] standard lexicographical ordering:簡單來說,字串的順序可以想像成「英語字典」中的單字,第一個字母、以 a、b、c ... 到 z 的順序排列;如果第一個字母一樣,那麼比較第二個、第三個乃至後面的字母。需要注意的是,大小寫字母 unicode 裡的順序是不一樣的。

「自動轉型」一直是 JavaScript 初學者很容易搞混的地方,希望透過今天的介紹可以幫助各位理解,如果覺得不過癮的話,還可以參考「JS 真值表

https://ithelp.ithome.com.tw/upload/images/20171210/200655045efx3QEMD2.png
保證讓你剛入門就放棄 (欸
別擔心,正常情況下,大部分的奇怪狀況你應該都不會遇到。

那麼感謝各位一路看到最後,以上就是今天分享的內容。


上一篇
重新認識 JavaScript: Day 06 運算式與運算子
下一篇
重新認識 JavaScript: Day 08 Boolean 的真假判斷
系列文
重新認識 JavaScript37

1 則留言

3
fysh711426
iT邦研究生 3 級 ‧ 2017-12-10 22:33:38

大大的文章寫得真好,
這段程式我自己實驗答案是 10,是各家瀏覽器實作的問題嗎?

console.log( -b );      // -10

還有這裡應該是大大筆誤了 XD
改成 null === undefined 之後,得到的會是正確的 true

console.log( -b ); 那段確實是我複製時貼錯了,負負得正,所以是 10 才對。

已修正,謝謝提醒 XD

另外, nullundefined=== 的結果會是 false 喔 (已更新)

https://ithelp.ithome.com.tw/upload/images/20171210/20065504xGEVTX6U10.png

我要留言

立即登入留言