弱型別的代價 – WAT!
July 21, 2022在程式語言的分類上,有所謂強型別(Strong type)、弱型別(Weak type)語言,強弱之別是相對的,沒有絕對之分野,端看語言對型態檢查的嚴格程度、型態轉換規則是否多元。
強型別
靜態定型語言不一定就是強型別語言,例如 Scala 可以使用 implicit
來定義隱式轉換(Implicit conversion)規則,願意的話,可讓整個語言看來偏向弱型別;動態定型語言也不一定就是弱型別,舉例來說,Ruby、Python 就偏向強型別語言。
Java 偏向強型別語言,在 Java 中運算或操作,較少有自動之型態轉換,舉例來說,字串在 Java 中不能直接作以下之操作:
String number1 = "3";
String number2 = "2";
int result = number1 - number2;
在偏向強型別的語言中,多數情況下型態轉換或剖析必須明確指定。例如:
String number1 = "3";
String number2 = "2";
int result = Integer.parseInt(number1) - Integer.parseInt(number2);
並非偏向強型別的語言,就不會發生自動型態。例如,Java 中的 +
運算子,只要有運算元有一個是字串,就會嘗試將另一個運算元轉為字串,而後進行字串串接:
String number = 1 + "23"; // "123",基本型態按字面轉為字串
Person person = new Person();
String description = "Person: " + person; // 呼叫 person 的 toString()
弱型別
JavaScript 偏向弱型別語言,字串的減法操作是可行的:
var result = '3' - '2';
console.log(result); // 1
console.log(typeof result); // number
偏向強型別的語言,多數情況下必須明確進行型態轉換或剖析,避免了許多非預期的自動型態轉換造成的錯誤,然而帶來了語法上的冗長,弱型別語言則相反,取得了語法簡潔的優點,但必須多注意非預期型態轉換帶來的問題。
JavaScript 的基本型態 number
、string
與 boolean
,會在必要的時候,自動型態轉換為對應的包裹物件 Number
、String
與 Boolean
。例如:
> var number = 10;
undefined
> number.toString(2);
'1010'
> (10).toString(2);
'1010'
>
number
變數指定的 10,型態是 number
,但在操作的 toString
物件才會有的方法,此時會自動使用 Number
實例來包裹 10 這個 number
,因此才可以操作 toString
。如果是實字表示,則可以加上 ()
後直接操作 toString
, 同樣會為你自動進行型態轉換。
類似地,在對 string
作操作時,若必要,也會自動包裹為 String
的實例。例如:
> 'caterpillar'.toUpperCase();
'CATERPILLAR'
>
toUpperCase
是 String
上定義的方法,執行環境必要時,會將 string
使用 String
包裹,才讓你操作 toUpperCase
方法。
你可以直接建立包裹物件。例如:
> typeof 10;
'number'
> typeof 'caterpillar';
'string'
> typeof new Number(10);
'object'
> typeof new String('caterpillar');
'object'
>
關於 Number
、String
與 Boolean
上可操作的方法,可參考:
你也可以使用 parseInt
、parseFloat
將字串轉換為數值,其好處是會自動忽略字串尾端非數字部份。例如:
> parseInt('10 years old...XD');
10
> parseFloat('3.14159......');
3.14159
> parseInt('0x10');
16
> parseInt('010');
10
> parseInt('010', 10);
10
> parseInt('010', 8);
8
>
後兩個 parseInt
分別指定了基數為 10 進位或 8 進位,如果字串以 0x 開頭,基數預設為 16 進位,以 0 開頭,建議直接指定基數為 10 或 8 進位,其他字串則預設為 10 進位。
對於字串,如果代表數字的話,可使用 +
、-
、*
、/
來作運算,不過要注意,+
優先作字串的串接,而非轉換為數字作數值加法,-
、*
、/
則會轉換為數字。例如:
> '6' + '2';
'62'
> '6' - '2';
4
> '6' * '2';
12
> '6' / '2';
3
>
在 JavaScript 中若結合布林值作 +
、-
、*
、/
等運算,true
會被當作 1,而 false
會被當作 0。例如:
> 1 + true;
2
> 1 + false;
1
>
布林值很有趣,在真假判斷式中,所有東西都可以轉換為布林值。一個口訣是 …
除了 0、
NaN
、''
、null
、undefinied
是假的(false
)之外,其他都是真的(true
)。
0、NaN
、''
、null
、undefinied
就是所謂 False Family 成員。
例如,若物件上不存在某個特性,直接取用該特性會得到 undefined
的值,所以若想知道某物件上是否存在該特性,則可以如下:
> var o = {};
undefined
> o.x ? 'has x' : 'has no x';
'has no x'
> o.x = 10;
10
> o.x ? 'has x' : 'has no x';
'has x'
>
在布林值判斷式中,如果取得 undefined
,則會當作 false
。如果你想避免 x
被設為 0、NaN
、''
、null
而造成誤判,則可以作更嚴格的檢查。例如:
function hasX(obj) {
return typeof(obj.x) !== 'undefined';
}
console.log('has x? ' + hasX({})); // has x? false
console.log('has x? ' + hasX({x : 10})); // has x? true
型態轉換也會發生在相等性比較時,在 JavaScript 中有兩個相等性運算子 ==
與 ===
,都可以判斷值或物件參考是否相同,簡單來說,前者會嘗試將 ==
兩邊轉換為同一型態,再比較是否相等,但後者只要 ===
兩邊型態不一,就會判斷為 false
。例如:
> '' == 0;
true
> '' === 0;
false
> null == undefined;
true
> null === undefined;
false
> 1 == true;
true
> 1 === true;
false
>
簡單來說,==
執行較寬鬆的比較,可允許型態轉換後的比較,===
執行較嚴格的比較,型態必須相同才有可能為 true
(當然,!=
與 !==
則是不相等的比較)。建議執行嚴格的比較,也就是使用 ===
與 !==
來比較相等或不相等。
在弱型別語言中,型態轉換自動發生的規則多,若是不確定,最好還是實際測試了解結果,避免不必要的型態轉換而發生誤判或錯誤的運算結果。
WAT!
以下來看幾個初學者容易 WAT 脫口而出的例子:
> [] + [];
''
> [] + {};
'[object Object]'
> {} + [];
0
> {} + {};
NaN
>
這個例子其實是來自 Gary Bernhardt 在 CodeMash 2012 時的一場 WAT 閃電秀,有時間的話,上去看看笑一笑吧!