增強的數值與字串

July 31, 2022

JavaScript 中的基本資料型態包括了數值、字串與布林值,型態名稱分別為 numberstring 與布林值 boolean,在 ES6 以後,多了個 symbol,ES11 以後,多了 bigint,這會在之後說明。

數值

至於 number 的部份,ES6 以後可以使用 Number.MAX_SAFE_INTEGERNumber.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
>

只不過,這就與 NumbertoString 字串不一致了,因為它少了 0b 或 0o 的前置:

> (4).toString(2)
'100'
> (63).toString(8)
'77'
> parseInt('100', 2)
4
> parseInt('77', 8)
63
>

NumbertoString(),還是得配合 parseInt,不過,ES6 以後的 Number 上有個 parseInt 也有個 parseFloat(),與全域的 parseIntparseFloat 是相同的:

> Number.parseInt === parseInt
true
> Number.parseFloat === parseFloat
true
>

因為 ES6 以後想要遠離全域的束縛,一些原本是全域函式的東西,都被放到了某個名稱之下,不過,有些獲得了增強,例如 Number.isNaNisNaN 行為上是不同的,只有 NaN 能讓 Number.isNaNtrue 了:

> Number.isNaN(NaN);
true
> Number.isNaN(1 / 'two');
true
> Number.isNaN('caterpillar');
false
> Number.isNaN(undefined);
false
> Number.isNaN('0');
false
>

Number.isFiniteisFinite 也不同,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
> 

分享到 LinkedIn 分享到 Facebook 分享到 Twitter