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

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

讓我們再次重新認識 JavaScript!

運算子系列總算要來到最終篇了。
如同先前所說,礙於篇幅的關係只會列出常見、以及大家在學習 JavaScript 最容易被搞混的部分。
如果要了解完整的內容可詳閱: MDN-運算式與運算子

指派運算子 (Assignment Operator)

在前面的文章中,我們曾經提到過 ++ -- 運算子,分別為我們處理變數的「遞增」與「遞減」。

那麼如果我們要處理的運算,不是 「+1」,而是「+2」、「+3」的話,有沒有更簡便的寫法呢?

這時候就可以利用指派運算子 (Assignment Operator) 來幫助我們。

最基本的指派運算子就是大家都很熟悉的 = ,一個等號來表示。

var a = 10 * 100;

像上面範例中,我們用 = 符號將右側的運算式結果「指派」(assign) 至等號左側的變數 a

除了最基本的用法之外,還可以結合數學運算子:

var a = 10;
a += 100;           // 代表 a = a + 100;
console.log( a );   // 110

a += 100 代表 a = a + 100 也就是說,會先將 = 右側的 a + 100 計算完成後,然後再指定至變數 a 內。

當然,行爲也會與數學運算子一樣的是, += 前後都是「數值」或「布林值」的話,則會視為數字後相加,而若有其中一方為字串,則會視為字串來連結。 這部分前面也已介紹過就不再贅述。

下面簡單列出常見的指派運算子:

逗號運算子

「逗號運算子」主要的工作讓逗號分隔運算式可以循序執行 (由左至右) ,並且會回傳最後一個運算式的值。

如果需要在一個運算式裡面同時包含多組運算式的話,就可以用「逗號」 , 來將它們分開。

最常出現的地方應該是 for 迴圈:

for (i = 0, j = 10; i < 10; i++, j++) {
  k = i + j;

透過 , 運算子可以將多組運算式看做是一個。

另外還有個常見的場景,就是「宣告」變數的時候:

var a = 10;
var b = 10;

像上面的 ab 可以寫成:

var a = 10, b = 10;

這樣就可以同時宣告多組變數,且給予預設值。

但是,有個需要注意的地方就是,有些朋友可能會寫成這樣:

var a = b = 10;
console.log(a);     // 10
console.log(b);     // 10

喔喔! 同時宣告兩個變數,而且 ab 同時都給予 10 的預設值了!

逼逼! 像這樣看似很方便的寫法,其實是有大問題的!

那麼 var a = b = 10; 出了什麼問題呢?

一開始我們有說過,沒有 var 宣告的變數都會變成「全域變數」對吧?

你可能會說 var a = b = 10; 有啊,前面有 var,但事實上,這段程式碼拆來來看是這樣的:

b = 10;
var a = b;

看出來了嗎? 實際上變數 b 是沒有透過 var 來做宣告的。

換言之,你可能在無意間創造了一個全域變數,而你卻沒發現:

(function(){
  var a = b = 10
  console.log(a, b);    // 10, 10
})();
// 離開了變數作用範圍的 a 會變成 undefined
// 而 b 卻變成全域變數而保留了狀態
console.log(a, b);    // undefined, 10

往後我們會介紹到變數的作用範圍,這裡讓各位有個印象即可。

邏輯運算子 (Logical Operator)

再來是今天的重頭戲:「邏輯運算子」。
如果你曾經用過其他程式語言來開發的話,你可能會覺得...

這有什麼好值得說明的,不就 AND &&、 OR || 還有 NOT ! 三種嗎?
運算後我應個會得到一個「boolean」的值,不是 true 就是 false 嘛。

如果你也是這樣想的話,那麼接下來的部分很值得繼續看下去。

來看看這份範例:

var a = 123;
var b = "abc";
var c = null;
console.log( a && b );        // "abc"
console.log( a || b );        // 123
console.log( c && a );        // null
console.log( c || b );        // "abc"
console.log( c || a );        // 123

看到了嗎? 說好的 truefalse 呢?

在講解前上面的詭異狀況前,先來說明一下,

「AND &&」:用兩個 & 符號來表示,在「多數程式語言」中代表的意義是:
「(條件/運算式A) && (條件/運算式B)」當 && 左右兩側的值同時為 true 時,則會得到 true 的結果。 若其中一方是 false 的情況下,則得到 false

「OR ||」:用兩個 | (pipe) 符號來表示,在「多數程式語言」中代表的意義是:
「(條件/運算式A) || (條件/運算式B)」當 || 左右兩側的值只要有一方為 true,則結果為 true。 只有在兩側皆為 false 的情況下才會得到 false

「NOT !」:以一個 ! 驚嘆號來表示,原本是 true 的結果經過 ! 轉換後會得到 false,而 false 會變成 true。 所以你可能會看到很多人用 !!xxx 來取代 Boolean(xxx),透過兩次的「NOT」操作,即可判斷某數值 Boolean 轉換後的結果。

嚴格來說,只有「NOT !」運算子才會回傳 truefalse

你可能會說,可是我在 if 條件式裡面,代入

if( a && b ) { ... }
if( a || b ) { ... }

這類的寫法,都可以正常執行啊?

在 JavaScript 這門程式語言當中,我們可以分成兩種「值」:

  • 那些經過 ToBoolean 轉換後得到 false 的值
  • 以及其他的值,通常這些最後都會變成 true

    這不是在講廢話,我知道你看完都硬了,快收起你的拳頭。

    Falsy 與 Truthy: 論 Boolean 的型別轉換

    前面講過,JavaScript 這門程式語言,我們可以分成兩種「值」,第一種就是經過 ToBoolean 轉換後會變成 false 的部分:

    Undefined +0, -0, or NaN
  • 空字串 ""'' 來源: ECMAScript® 2017 Language Specification: 7.1.2 ToBoolean

    不是我在豪小,真的有規範對吧。

    如果是上面列出的幾種情況,那麼透過 ToBoolean 轉換就會變成 false,而其他的部分都會是 true

    而那些轉換後會得到 false 結果的,我們通常稱這些叫 「falsy」值,而其他會變成 true 的部分,則是 「truthy」值

    聽起來好像很好理解嘛,讓我們來猜猜下面程式片段會得到什麼:

    Boolean("false")     // ?
    Boolean("0")         // ?
    Boolean("''")        // ?
    

    猜對了嗎? 答案都是 true

    裡面只有 "''" 看起來比較像 false,但規範裡寫著是「空字串」 ""'',可不是「雙引號包覆單引號」喔。

    然而還有一些容易讓人搞混的地方:

    Boolean( {} )
    Boolean( [] )
    Boolean( function(){} )
    

    以上這些也都是 true

    所以再複習一次:

  • 那些經過 ToBoolean 轉換後得到 false 的值
  • 以及其他的值,通常這些最後都會變成 true

    判斷 false 比判斷 true 要來得簡單對吧!

    再回到邏輯運算子

    事實上,「AND &&」與「OR ||」分別代表「且」與「或」的意思沒錯,但一開始的範例為什麼會是:

    var a = 123;
    var b = "abc";
    var c = null;
    console.log( a && b );        // "abc"
    console.log( a || b );        // 123
    console.log( c && a );        // null
    console.log( c || b );        // "abc"
    console.log( c || a );        // 123
    

    說好的 truefalse 呢? 誰跟你說好

    來看看 ECMAScript: 12.13Binary Logical Operators 規範怎麼說:

    The value produced by a && or || operator is not necessarily of type Boolean. The value produced will always be the value of one of the two operand expressions.

    簡單來說,透過 &&|| 所產生的值不一定會是 Boolean,而是兩者其中之一

    &&|| 運算子在判斷的時候,會先對左邊的數值進行檢查。

  • 如果是 Boolean 類型就再做後續的判斷,如果不是?那就會透過 ToBoolean 判斷是「falsy」或「truthy」來轉換成對應的 truefalse
  • || 運算子來說,若第一個數值轉換為 true,則回傳第一個數值,否則回傳第二個數值。
  • && 運算子來說,若第一個數值轉換為 true,則回傳第二個數值,否則回傳第一個數值。
  • 所以,在 if 條件判斷當中,JavaScript 會針對回傳後的數值再度做 ToBoolean 判斷是「falsy」或「truthy」,
    這也就是為什麼在 &&|| 的結果可以用來當作 truefalse 的判斷了。

    所以說,未來如果看到這類想騙人的題目:

    !!'false' ==  !!'true'    // ?
    !!'false' === !!'true'    // ?
    

    相信你應該可以知道答案是什麼了吧!

    那麼以上就是今天分享的重點,希望各位在遇到「真假」轉換的時候不會再上當受騙。
    下回我們要繼續來介紹流程控制的部分,感謝大家收看。 掰。

    1.那些經過 ToBoolean 轉換後得到 false 的值
    2.以及其他的值,通常這些最後都會變成 true

    我搞不太懂,這邊提到的值,跟前面陣列、物件章節提到的基本型別值(如:string、boolean、number)、物件值。

    這兩篇提到的「值」名詞解釋上的差異在哪呢?

    你好,JavaScript 以變數型態來說,主要分為「基本型別」與「物件型別」,這個部分相信不用我多做解釋了。

    而不管是基本型別或物件型別的變數,兩者都可以透過 ToBoolean 轉換成 truefalse 的「值」。

    所以我們可以直接將某個變數做為流程判斷的條件,如:

    let obj = {};
    if ( obj ) {
      // ...
    

    這篇文章想說明的是,哪些東西經過轉型後會變成 true,而哪些又會變成 false

  •