增強的數值與字串
July 31, 2022JavaScript 中的基本資料型態包括了數值、字串與布林值,型態名稱分別為 number、string 與布林值 boolean,在 ES6 以後,多了個 symbol,ES11 以後,多了 bigint,這會在之後說明。
數值
至於 number 的部份,ES6 以後可以使用 Number.MAX_SAFE_INTEGER 與 Number.MIN_SAFE_INTEGER 來取得可表達的最大整數與最小整數,而 Number.isSafeInteger 可用來檢查一個整數是否在安全範圍內:
> Number.MAX_SAFE_INTEGER;
9007199254740991
> Number.MIN_SAFE_INTEGER;
-9007199254740991
> Number.isSafeInteger(9007199254740992);
false
> Number.isSafeInteger(9007199254740991);
true
> Number.isInteger(9007199254740992)
true
>
Number.isInteger 只檢查數字是不是整數,不會考慮它是不是安全範圍內的整數。至於兩個符點數之間,最小的差距,可以使用 Number.EPSILON 來取得:
> Number.EPSILON;
2.220446049250313e-16
>
可以使用 0b(或 0B) 撰寫二進位數來表示數字,或者是 0o(或0O?別這麼寫啦!)撰寫八進位數來表示數字。例如:
> 0b0100
4
> 0o77
63
>
(在 ES5 嚴格模式下,不允許使用八進位 077 來表示 63,因為有人會誤用 0 來對齊數字。)
如果你有個字串 '0b0100'、'0o77',可以使用 Number(而不是使用 parseInt)來轉換為 10 進位數字,例如:
> Number('0b0100')
4
> Number('0o77')
63
>
只不過,這就與 Number 的 toString 字串不一致了,因為它少了 0b 或 0o 的前置:
> (4).toString(2)
'100'
> (63).toString(8)
'77'
> parseInt('100', 2)
4
> parseInt('77', 8)
63
>
Number 的 toString(),還是得配合 parseInt,不過,ES6 以後的 Number 上有個 parseInt 也有個 parseFloat(),與全域的 parseInt 與 parseFloat 是相同的:
> Number.parseInt === parseInt
true
> Number.parseFloat === parseFloat
true
>
因為 ES6 以後想要遠離全域的束縛,一些原本是全域函式的東西,都被放到了某個名稱之下,不過,有些獲得了增強,例如 Number.isNaN 與 isNaN 行為上是不同的,只有 NaN 能讓 Number.isNaN 為 true 了:
> Number.isNaN(NaN);
true
> Number.isNaN(1 / 'two');
true
> Number.isNaN('caterpillar');
false
> Number.isNaN(undefined);
false
> Number.isNaN('0');
false
>
Number.isFinite 與 isFinite 也不同,Number.isFinite 遇到非 number 就會是 false:
> isFinite('15')
true
> Number.isFinite('15')
false
> Number.isFinite(15);
true
> Number.isFinite(Infinity);
false
>
字串
在字串的部份,ES6 以後如果字串中的字元不有 0000 ~ FFFF 的範圍之中(也就是 BMP 外的字元),例如高音譜記號的 1D11E,可以直接使用 \u{1D11E} 來表示了,使用 '\uD834\uDD1E' 也是可以啦!
> '\u{1D11E}' === '\uD834\uDD1E';
true
>
ES6 以後支援模版字串(Template string),必須使用 ` 來建立模版字串,不少文件最愛舉的例子是它可以換行,例如,有個 helloworld.js 檔案寫了以下的內容的話:
let html = `<!DOCTYPE html>
<html>
<head>
<title>Hello, World</title>
</head>
<body>
Hello, World
</body>
</html>`;
console.log(html);
執行這個 .js 檔案你會看到:
C:\workspace>node --use_strict helloworld.js
<!DOCTYPE html>
<html>
<head>
<title>Hello, World</title>
</head>
<body>
Hello, World
</body>
</html>
該換行的都換行了,簡單來說,它會保留 ` 之間的內容。例如:
> let s = `Your left brain has nothing right.
... Your right brain has nothing left.`
undefined
> s;
'Your left brain has nothing right.\nYour right brain has nothing left.'
> typeof s;
'string'
>
一個模版字串的型態也是 string。模版字串中若 ${} 的部份,會執行 ${} 中的內容,然後取得結果再與其他字串結合在一起,例如:
> `1 + 2 = ${1 + 2}`
'1 + 2 = 3'
> let a = 1;
undefined
> let b = 2;
undefined
> `${a} + ${b} == ${a + b}`
'1 + 2 == 3'
> let o = {p : 10};
undefined
> `${o.p}`;
'10'
> let arr = [1, 2, 3];
undefined
> `Double arr: ${arr.map(n => n * 2)}`;
'Double arr: 2,4,6'
>
最後一個看到的是 ES6 以後支援的箭號函式(Arrow function),之後就會談到。可以想見的,如果想要建立模版,例如 HTML,使用模版字串很方便,例如:
> let title = 'Hello, World';
undefined
> let message = 'Hello? World?';
undefined
> let html = `<!DOCTYPE html>
... <html>
... <head>
... <title>${title}</title>
... </head>
... <body>
... ${message}
... </body>
... </html>`;
undefined
> console.log(html);
<!DOCTYPE html>
<html>
<head>
<title>Hello, World</title>
</head>
<body>
Hello? World?
</body>
</html>
undefined
>
標記模版
經常被拿來在說明模版字串之後提及的是標記模版(Tagged template),如前面提過的,模版字串中若 ${} 的部份,會執行 ${} 中的內容,然後取得結果再與其他字串結合在一起,如果你想分別處理其他字串以及 ${} 運算結果,例如 ${} 可能來自使用者輸入,而你想要將有安全疑慮的字元替換掉,那標記模版(Tagged template)就會派上用場。
不過,標記模版其實是個函式呼叫的特殊形式,如果你有個函式 f,那麼:
let a = 10;
let b = 20;
f(`${a} + ${b} = ${a + b}`);
結果就相當於:
f('10 + 20 = 30');
然而,如果你使用:
f`${a} + ${b} = ${a + b}`
就會將 ${} 外的字串分割出來,使用陣列儲存,然後運算出 ${a}、${b}、${a + b} 的值,最後再用陣列與運算出來的值來呼叫函式,也就是相當於:
f(['', ' + ', ' = ', ''], 10, 20, 30);
接下來,就看你的函式中怎麼處理這些值了,例如:
function f(strings, value1, value2, value3) {
// 函式處理
}
如果你的標記模版中 ${} 數量是固定的,這樣就夠了,不過,若是事先無法決定個數,那麼可以定義以下這樣的函式:
function f(strings, ...values) {
// 函式處理
}
... 是 ES6 以後的 Rest 運算子,當它出現在參數時,會將呼叫函式時其餘的引數收集在一個陣列中,當成函式的引數傳入,因此就標記模版來說,就是將 ${} 的結果收集至陣列,然後當成第二個引數傳入函式了。
如果你想要 console.log 能顯示 '\n' 字樣,基本上在字串中要 escape:
> console.log('ABC\nEFG');
ABC
EFG
undefined
> console.log('ABC\\nEFG');
ABC\nEFG
undefined
> console.log(`ABC\nEFG`);
ABC
EFG
undefined
> console.log(`ABC\\nEFG`);
ABC\nEFG
undefined
>
ES6 以後有個 String.raw 函式,搭配標記模版,可以直接 escape,讓 \n 這類的字元,不會被轉譯為換行:
> String.raw`ABC\nEFG`
'ABC\\nEFG'
> console.log(String.raw`ABC\nEFG`)
ABC\nEFG
undefined
>
函式搭配標記模版時,函式的第一個參數值雖是陣列,然而會加上一個 raw 特性,可用來取得未轉譯字串的清單:
> function f(strings) {
... console.log(strings);
... console.log(strings.raw);
... }
undefined
> f`ABC\nEFG`
[ 'ABC\nEFG' ]
[ 'ABC\\nEFG' ]
undefined
>


