運算子,例如算術運算子+ - * /
或者邏輯運算子&&
、||
、!
,相信大家都看過了,所以這篇不會把所有內容都寫出來,而是針對某些部分作補充。例如作為新手,我雖然知道||
和&&
的意思,但不太熟悉||
的另一種用法(短路邏輯)。又例如++值
和值++
這兩種寫法有什麼分別等等。
因此,接下來會用兩篇來整理自己重新看此篇表達式與運算子MDN文檔時不熟悉的部分,希望同時也對大家有所幫助。這篇主要記錄其中三個運算子:算術運算子、賦值運算子和比較運算子的知識。
在看運算子MDN文檔時,見到有一併提及表達式這個述語。為什麼明明在講運算子,但又把表達式混進來講呢?因為表達式有好幾種類型,而這些類型都是與運算子有關的。先看看簡單的定義:
表達式(expression):
意思是該段程式碼會產生一個值。
例如:x=7
、3+4
。
陳述式(statement):
意思是該段程式碼會執行一個動作。
例如:宣告變數var a
、函式、for迴圈、if else條件式。
在表達式的定義中,提到的x=7
、3+4
這兩個例子都有用到運算子。當中用到=
去賦予一個值,也有用+
去產生一個值。事實上,表達式有分多種類型,而當中不少都涉及到運算子,因為它們都會產生一個值,所以都是表達式類型之一。
表達式類型:
運算子較常見的類型有:
=
、+=
==
、<
、>=
++
、--
、**
&&
、||
、!
'Hello' + 'World!'
詳細種類可看MDN。
有一些特別類型,叫做一元、二元、三元(條件運算子)運算子,之後會再提及。
先從算術運算子的部分開始吧。
這部分的重點:
%
、**
++值
、值++
的分別+
、-
%
、**
除了一般的加減乘除,還有不時會見到,但自己又不太熟悉的%
和**
,所以也要重溫一下。%
是指求餘,就是指兩個數值相除時得出的餘數:
console.log(5 % 2); //1
console.log(7 % 7); //0
//簡寫例子
let x = 15;
console.log(x %= 6); //即是 x = x % 6。回傳3
let y = 10;
console.log(y %= 0) //即是 y = y % 0。回傳NaN
**
是指數的意思:
console.log(10 ** 2); //100
console.log(3 ** 0); //1
//簡寫例子
let a = 2;
console.log(a ** 2); //4
console.log(a ** 'hello'); //NaN
加減乘除時,如果涉及到字串,就會有自動轉換型別的情況。
例如這些例子:
這裏只有+
,才會當作字串一樣去連接起來。在+
的世界裏,如果其中一個運算數是字串,就會把不是字串的另一方轉成字串。針對這些情況,我們也看看以下比較特別的例子:
// 其中一方是字串的例子
'hello' + 123 // "hello123"
'123' + true // "123true"
'123' + null // "123null"
'123' + undefined // "123undefined"
'123' + [] // "123"
'123' + {} // "123[object Object]"
// 雙方都不是字串的例子
123 + [] // "123"
//因為{}是物件,會被轉成字串這個基本型別,再與123相加
123 + {} // "123[object Object]"
//陣列以join(',')的方式被轉為基本型別,變成"1,2,3,4,5"
123 + [1,2,3,4,5] // "1231,2,3,4,5"
123 + null //123
123 + undefined //NaN
123 + true //124
物件的轉型規則是:除了date是用toString()
去轉型,其他都會用valueOf()
,如果valueOf()
找不到基本型別值,就會用toString()
,也是最常見的做法。
如下圖顯示,因為valueOf()
找不到{}
和[]
的基本型別值,所以會用toString()
值++
和++值
的分別算術運算子還包括一元運算子,例如遞加++
和遞減--
,但放在前面後放在後面又會有什麼分別呢?
let x = 10;
let y = 10;
console.log(x++) //10
console.log(++y) //11
console.log(x); //11
console.log(y); //11
由此可見,其實x
和y
一樣都是11
。
當我們在變數前或後加上++
,才會出現差別。
++
放在前面:
回傳+1
後的值
++
放在後面:
回傳原本的數值
如果一元運算子+
和-
後的值不是數字型別,就會用Number()
把它轉成數字型別。如果是物件,就會用valueOf()
去轉型,再按+
、-
來取得數值:
let a = '1';
let b = '-1';
let c = 'abc';
let d = [];
let e = {};
let f = null;
let g = undefined;
console.log(+a,-a) // 1, -1
console.log(+b,-b) // -1, 1
console.log(+c,-c) // NaN, NaN
console.log(+d,-d) // 0, -0
console.log(+e,-e) // NaN, NaN
console.log(+f,-f) // 0, -0
console.log(+g,-g) // NaN, NaN
這部分的重點:
a = b = c
的寫法在看賦值運算子這個部分時,看到a = b = c
這種寫法。我學習JavaScript時很少會看到這樣寫(新手表示真的不知道QQ),所以也記錄一下。
let a = 10;
let b = 11;
let c = 12;
a = b = c; //相等於 a = (b = c);
console.log(a,b,c) //12, 12, 12
再複雜一點:
let a = 10;
let b = 11;
let c = 12;
a += b *= c
console.log(a,b,c) //142, 132, 12
//逐步拆解
// a += (b *= c)
// a += (b = b*c)
// a = a + (b = b*c)
除此之外,另一個不太熟的是解構賦值這個課題,但這個已經可以開新一篇去寫了,所以在之後會再談及~
這部分的重點:
==
相等比較與自動轉型==
和===
的差異==
相等比較與自動轉型==
或 !=
相等比較(較寛鬆)===
或 !==
全等比較(較嚴謹)用較寛鬆的相等比較==
或!=
之前,如果兩邊的值不是相同型別,就會先把某一個/全部值轉成同一型別,才會去做相等比較。我們可以重看之前文章中曾經引用過的這張圖表:
截圖自MDN
如果看懂這個表,以下例子就很易懂:
10 == '10' //true
'10' == true //false
10 == true //false
false == 0 //true
true == 1 //true
true == 'true' //false
false == 'false' //false
以上的例子中,我曾經很直覺地想:「'true'
不是true
嗎?因為它不是空字串,為什麼不是等於true
?」但注意,這裏並不是把'true'
轉成布林值來作比較,而是把兩邊都轉成數字,才去做比較。所以,true
會變成1
,true
會變成NaN
,所以這裏比較的是1 == NaN
,答案就是false
。
[] == 0 //true
[] == '' //true
[''] == 0 //true
[0] == 0 //true
[0] == '' //false
[] == [] //false
{} == {} //false
//以上兩個例子,之前的文章有詳細解釋過
null == undefined //true
前5個例子中,按規則都需要把左邊的物件轉成基本值,意思是用toString()
或valueOf()
的方法找出物件的基本值。
以[]
為例,valueOf()
只返回原本的陣列,找不到基本值,所以我們要用toString()
方法,並得出""
。繼而我們再照字串比較數字的規則,把""
轉成數字0
,所以答案會是true
。
[] == []
和{} == {}
因為記憶體地址不同,所以會是false
,詳細解釋可看之前的文章。
另外要注意的是null
和undefined
是相等的。
==
和===
的差異為了避免==
會產生null == undefined
這些情況,很多開發者都主張用===
而非==
,但我們也需要了解清楚它們之間的分別。You don't know JS 的作者在書中提及:
A very common misconception about these two operators is: "== checks values for equality and === checks both values and types for equality.” While that sounds nice and reasonable, it’s inaccurate. Countless well-respected JavaScript books and blogs have said exactly that, but unfortunately they’re all wrong.
The correct description is: "== allows coercion in the equality comparison and === disallows coercion.”
作者指出,如果說「==
會檢查兩邊的值是否對等,而===
會一併檢查兩邊的值和型別是否對等」,這個說法是錯的。正確的說法是「==
會容許在比較時自動轉型,但===
不容許在比較時自動轉型。」
In the first explanation, it seems obvious that === is doing more work than ==, because it has to also check the type. In the second explanation, == is the one doing more work because it has to follow through the steps of coercion if the types are different.
If you want coercion, use == loose equality, but if you don’t want coercion, use === strict equality.
The implication here then is that both == and === check the types of their operands. The difference is in how they respond if the types don’t match.
之後作者再強調,如果你想有自動轉型,就用==
,否則就用===
。==
和===
都會檢查型別,差異在於當型別不同時,==
和===
的反應(即是會,還是不會幫它們自動轉型)。
有興趣可看看原文。
這篇主要是補充自己對表達式和陳述式、算術運算子、賦值運算子和比較運算子課題上的知識。
明天會繼續承接運算子這個課題,針對講解邏輯運算子,例如||
和&&
除了放在if else
判斷式外的幾種用途,也會提及條件運算子條件 ? 值1 : 值2
,感謝你的閱讀~
JavaScript Addition Operator in Details
You Don't Know JS: Types & Grammar - Chapter 4. Coercion
重新認識 JavaScript: Day 07 「比較」與自動轉型的規則