iT邦幫忙

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

重新認識 JavaScript系列 第 6

重新認識 JavaScript: Day 06 運算式與運算子

前面花了好幾天的時間介紹變數與型別,接著我們繼續來要介紹的重點:運算式 (Expression) 與 運算子(Operator)。

運算式 (Expression) 與 運算子(Operator)

JavaScript 的語法基本上可以分為兩大類,「敘述句 (Statement)」「運算式 (Expression)」

  • 敘述句 (Statement):
    簡單來說,敘述句就是執行某個動作。 像是變數的宣告、賦值,迴圈和 if 判斷式等等都可以被歸類於此。
var foo;
  • 運算式 (Expression):
    而運算式最大的特性,就是它會產生一個「值」。
    像是我們在呼叫 function 時的參數 (arguments),或者透過 = 賦值時,在 = 「右側」的部分都屬於運算式的部分。
var a = 10 * 10;

上例 = 右側的 10 * 10 就是運算式。


在運算式中,會透過提供一些數值給「運算子」(Operator) 進行運算,進而得到一個運算的結果。
例如我們從小就學過的四則運算「加、減、乘、除」都是屬於運算子的一種。

運算子的類型很多,依照性質來分類,大致上可以分成下面幾種:

  • 算術運算子 (Arithmetic Operator)
  • 指派運算子 (Assignment Operator)
  • 位元運算子 (Bitwise Operator)
  • 比較運算子 (Comparison Operator)
  • 邏輯運算子 (Logical Operator)
  • 字串運算子 (String Operator)
  • 特殊運算子 (Special Operator)

由於篇幅的關係,系列文只會針對常見以及容易搞混的部分來做解說。

更詳細的內容各位可以參閱 MDN: 運算式與運算子


算術運算子

JavaScript 各種運算子當中,最常見的就屬「算術運算子」了。
簡單來說,算術運算子包括了大家所熟知的數學四則運算「加、減、乘、除」等。

一般來說,四則運算的算術運算子在多數的程式語言中,應該可以算是最單純的算術運算子,但是在 JavaScript (更準確的說法應該是 ECMAScript) 當中,運算後的結果可能會跟你所想的不太ㄧ樣。


加號 (+)

加號 + 的使用非常簡單,如果你想要表示 1 + 2 這個算式的話,可以這樣寫:

var a = 1 + 2;
console.log(a);      // 3

如果加號 + 前後雙方都是「數字」的話,確實是最單純的情況。
但我們前面在介紹變數的時候,有提到「特殊的數字」這種東西 (忘記的朋友請回頭參閱:Day 03 變數與資料型別)。

什麼是「特殊的數字」?

Infinity-Infinity,以及 NaN 都屬於「特殊的數字」。

先看 Infinity 系列:

Infinity + Infinity      // Infinity

-Infinity + -Infinity    // -Infinity

-Infinity + Infinity     // NaN

NaN 的話,則是只要有其中一個是 NaN,那麼結果就必定是 NaN

10 + NaN            // NaN

Infinity + NaN      // NaN

-Infinity + NaN     // NaN

到目前為止還只是單純數字的狀況。 那麼,假設加號 + 兩側的其中一方,不是數字而是「字串」呢?

100 + "100"      // "100100"

100 + "ABC"      // "100ABC"

"ABC" + "XYZ"    // "ABCXYZ"

在上面的範例當中可以看到,當加號 + 兩側的其中一方是字串的情況下,加號 + 會將兩者都視為「字串」連接在一起。
也就是說,其中一方是字串,另一端會被「自動轉型」為字串後,連接在一起。

numberbooleanobject 的情況來說,轉型時會去呼叫它們的 .toString() 的 「原型方法」[註1] 去取得對應的字串。
nullundefined 則是透過 JavaScript 的 String() 函數來將它們分別轉為 "null""undefined"

10 + 'abc'          // "10abc"
true + 'abc'        // "trueabc"

100 + {}            // "100[object Object]"

// 當數字與 undefined 相加時,undefined 會被嘗試轉成數字,也就是 NaN
123 + undefined     // NaN
"abc" + undefined   // "abcundefined"

// 當數字與 null 相加時,null 會被嘗試轉成數字,也就是 0
123 + null          // 123
"123" + null        // "123null"

另外,在一個很長的運算式中,可能會有「數字」與「字串」的混搭算式,如:

var num1 = 10;
var num2 = 100;

var str = "10 加 100 的數字會是" + num1 + num2;

猜猜看,此時 str 的結果會是什麼?

答案是 「 "10 加 100 的數字會是10100" 」。

會有這樣的結果是由於運算式的計算是「由左而右」且「先乘除後加減」的模式來運算。 也就是說,前面的字串會先與 num1 相加,再把結果與 num2 做相加。

若要避免這樣的問題時,可以在 num1 + num2 的算式中,用小括號 ( ) 包覆起來:

var num1 = 10;
var num2 = 100;

var str = "10 加 100 的數字會是" + (num1 + num2);

str 的結果就會是預期中的「"10 加 100 的數字會是110"」了。


減號 (-)

再來是減號 -。 如同前面的加法一樣,如果只是單純的數字算式:

var a = 10 - 3 ;
console.log(a);      // 7

那麼結果就會是單純的數值運算。

如果是「特殊的數字」之 Infinity 系列:

Infinity - Infinity      // NaN
-Infinity - -Infinity    // NaN

-Infinity - Infinity     // -Infinity
Infinity - -Infinity     // Infinity

如果其中一方是 NaN 的話,那麼結果必定是 NaN

另外,與加號 + 不同的是,當其中一方不是「數字」的情況下:

  • 基本型別 (如 stringbooleanundefinednull)
    在做減法運算時,若其中一方屬於基本型別且不是數字的情況,那麼 JavaScript 會在先在背後透過 Number() 嘗試將數值轉為「數字」,再進行運算。
100 - "50"        // 50
100 - "abc"       // NaN

// false 經過 Number() 轉型成數字後,會變成 0
100 - false       // 100
// true 經過 Number() 轉型成數字後,會變成 1
100 - true        // 99

100 - undefined   // NaN
100 - null        // 100
  • 物件型別
    如果是物件型別的情況下,則是會先透過物件的 valueOf() 方法 [註2] 先求得對應的數值,如果得到 NaN,那麼結果就是 NaN
    如果物件沒有 valueOf() 方法的話,則是透過 toString() 先轉成「字串」後,再以 Number() 嘗試將數值轉為「數字」後進行運算。
// 簡單物件
100 - { }         // NaN
// 自訂物件,透過 Object.prototype.valueOf 來指定物件的 value

function Obj(number) {
  this.num = number;
};

Obj.prototype.valueOf = function(){
  return this.num;
};

var o = new Obj(50);

// 因為 o.valueOf() 的值為 50
100 - o         // 50

關於物件的 prototypevalueOf() 的部分,我們在往後文章會有詳細篇幅介紹,這裡只需要知道自動轉型的規則即可。


乘號 (*)

相較前面的加法、減法的規則,乘法運算子就單純許多。
乘法運算子由一個「星號」 * 來代表,用來計算前後兩個數值的乘積。

var a = 10 * 10 ;
console.log(a);      // 100

在前後兩者都是數字的情況下,計算結果就是兩個數值的乘積。
如果計算結果超出 JavaSCript 的數字範圍,那麼就會看結果是正數或負數來決定是 Infinity 或是 -Infinity

當然如果其中一個是 NaN的話,那麼結果必定也是 NaN
而依照 IEEE754 標準的規定, Infinity * 0 的結果也是 `NaN。

如果有其中一個不是數字的話,那麼 JavaScript 就會先在背後以 Nubmer() 作轉換後再進行計算,如:

100 * "10"      // 1000
100 * abc       // NaN

100 * true      // 100
100 * false     // 0

100 * {}        // NaN

除號 (/)

除法與乘法的規則類似。
除號在 JavaScript 用一個「斜線」/ 來表示。

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

在前後兩者都是數字的情況下,計算結果就是兩個數值的商。

但是,在被除數為 0 的情況下:

  • 除數為正數,則結果為 Infinity
  • 除數為負數,則結果為 -Infinity
  • 除數為 0,則結果為 NaN

當然,如果有其中一個是 NaN,則結果也會是 NaN

如果有其中一個不是數字的話,那麼 JavaScript 就會先在背後以 Nubmer() 作轉換後再進行計算。


取餘數 (%)

除了基本的四則運算之外,JavaScript 也有取餘數的運算子,以「百分比符號」 % 來表示。
使用方式與除號類似,但得到的值是除法運算後的「餘數」:

var a = 100 % 33;
console.log(a);      // 1

在前後兩者都是數字的情況下,計算結果就是除法運算後的「餘數」。

而被除數是 Infinity-Infinity 的情況下,則取餘數後結果都會是 NaN

Infinity % 0              // NaN
Infinity % 100            // NaN
Infinity % Infinity       // NaN
Infinity % -Infinity      // NaN

被除數是一般數值,而除數為 Infinity 的情況下,則結果為被除數:

100 % Infinity       // 100
0 % Infinity         // 0

被除數是一般數值,而除數為 0 的情況下,則結果也是 NaN
當然,如果有其中一個是 NaN,則結果也會是 NaN

與除法一樣的是,如果有其中一個不是數字的話,那麼 JavaScript 就會先在背後以 Nubmer() 作轉換後再進行計算。


  • [註1] 原型方法: JavaScript 與其他物件導向語言不一樣的地方是,它的繼承是 "prototype-base" 的。 也就是說,即便是基本型別的數值,除了 nullundefined 屬於特殊用途,並沒有相對應的原始型別包裹物件之外,都有它們對應的構造函數,或稱包裝器 (wrapper) 。 而這些「基本型別」的數值理論上是不會有對應的方法 (method),但可以由原始物件繼承而來。這部分在後續講到原型鍊時會有詳細說明。

  • [註2] valueOf() 是用來回傳特定物件相對應原始型別的值,當 JavaScript 的物件在進行運算時,都會透過 valueOf()toString() 方法,取回該物件對應的原始型別的值再進行運算。


沒想到四則運算也可以寫這麼長一篇,在後續的文章當中,我們會繼續來介紹 JavaScript 的其他運算子,以上就是今天分享的內容。


上一篇
重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
下一篇
重新認識 JavaScript: Day 07 「比較」與自動轉型的規則
系列文
重新認識 JavaScript37

1 則留言

0
hannahpun
iT邦新手 5 級 ‧ 2019-05-11 15:48:34

這篇釐清了好多觀念 大感謝

我要留言

立即登入留言