與眾不同的資料型態

July 21, 2022

在 JavaScript 中,有基本(Primitive)資料型態、複合(Composite)資料型態,還有兩個特殊的值 nullundefined

數值

單就 ES5 而言,基本資料型態包括了數值、字串與布林值,型態名稱分別為 numberstringboolean

ES11 新增了 bigint,這邊只談 ES5,稍後的文件再來談 bigint

在 ES5 以前,JavaScript 沒有整數與浮點數的區別,數值都是 〈IEEE 754 標準 64 位元浮點數,型態為 number,可表示的數字最小值為 Number.MIN_VALUE(5e-324),而最大值為 Number.MAX_VALUE(1.7976931348623157e+308)。

如果只用 number 來表示整數,位元組中可使用的範圍就是 52 個位元的分數部份,最大整數只能表示到 2⁵³,也就是 9007199254740992,然而 9007199254740992 這數字不安全,因為寫下 9007199254740993,結果還是代表著 9007199254740992:

> 9007199254740993
9007199254740992
>

這也就是為什麼 Number.MAX_SAFE_INTEGER 的值是 9007199254740991,這個數字在 JavaScript 裡才是用 number 表示整數時的安全整數。

注意位元運算子的結果會是 32 位元有號整數,像是 ~>> 等。

與其他程式語言相同,撰寫數值時要使用數值實字(Number literal),預設是十進位整數,也可以用 0 開頭表示八進位整數,或是用 0x 開頭表示十六進位整數。例如:

  • 10(十進位)
  • 0xFF(十六進位)

若要表示浮點數,則可使用科學記號表示。例如:

  • 3.14
  • 5.231E13(5.231 * 10¹³)
  • 1.31E-32(1.31 * 10⁻³²)

數值有幾個特殊值,例如 +Infinity(或簡單寫為 Infinity)表示正無限大,-Infinity 表示負無限大。也可以使用 Number.POSITIVE_INFINITY 取得正無限大,Number.NEGATIVE_INFINITY 取得負無限大。另外還有 NaNNumber.NaN 表非數值(Not a Number),例如你嘗試作 1 / 'two' 時,就會出現 NaN 的結果。

注意,NaN 不等於任何值,NaN 也不等於 NaN,如果你要測試某值結果是否為 NaN,方法之一可以用 isNaN 函式來測試。例如:

> NaN === NaN;
false
> isNaN(NaN);
true
> isNaN(1 / 'two');
true
> 

不過,isNaN 不可靠,如果傳入的數值是 NaN 確實會是 true,然而,無法轉換至數字的值,具體來說,也就是丟給 Number() 會傳回 NaN 的值,都會讓 isNaN 的結果為 true,像是非數字格式且非空的字串、undefined 等。例如:

> isNaN('caterpillar');
true
> isNaN(undefined);
true
> isNaN('0');
false
>

如果想測試某值是不是 NaN,最好自行撰寫,還好,因為 NaN 是 JavaScript 中唯一自身不等於自身的值,所以可以自己定義一個 isRealNaN 函式:

function isRealNaN(value) {
	return value !== value;
}

console.log(isRealNaN(NaN));           // true
console.log(isRealNaN('caterpillar')); // false
console.log(isRealNaN(undefined));     // false

問題並不會這麼結束,雖然 NaN 代表「Not a Number」,不過,typeof NaN 的結果卻是 'number',這實在很囧:

> typeof NaN;
'number'
> 

當然,在 JavaScript 中,typeof 本身就有些問題,不過,如果你想要檢查值是不是一個有限數字,可以借助 isFinite 函式,它對 NaNInfinity 測試的結果會是 false,不過,isFinite 同樣會將指定的值丟給 Number(),用它的傳回值做檢查,因此,isFinite('15') 的結果會是 true

如果你真的只想檢查一個 number 是否為有限數字,可以自己定義一個 isRealFiniteNumber 函式:

function isRealFiniteNumber(value) {
	return typeof value === 'number' && isFinite(value);
}

console.log(isRealFiniteNumber(NaN));       // false
console.log(isRealFiniteNumber(Infinity));  // false
console.log(isRealFiniteNumber('93'));      // false
console.log(isRealFiniteNumber(93));        // true

字串

在 JavaScript 中,字串是基本資料型態,可使用單引號或雙引號來包括一串文字,用以表示字串。例如:

var str1 = 'text1';
var str2 = "text2";

JavaScript 中沒有代表字元的型態,以單引號或雙引號來包括單一字元,都是字串型態。例如:

> typeof 'A';
'string'
> typeof "B";
'string'
> 

typeof 可以用來測試某資料的型態,會傳回型態的字串描述,例如,對數值使用 typeof 會傳回 'number'。對字串使用則傳回是 'string'。在撰寫字串實字(String literal)時可使用單引號或雙引號,一般習慣在 JavaScript 程式中採用單引號,而將雙引號保留給 HTML 使用,避免跳脫文字的麻煩。例如:

var html = '<input type="text" value="defalut">';

你可以使用 \uxxxx,指定 Unicode 碼點來表示字串,例如:

> '\u0048\u0065\u006C\u006C\u006F'
'Hello'
> '\uF9F4'
'林'
> '\u54C8\u56C9'
'哈囉'
>

Unicode?碼點?JavaScript 在 1995 創建之時,UTF-16 尚未公佈,只能採用 UCS-2,使用兩個位元組為當時字元集的字元編碼,後來支援 UTF-16,以便能處理 U+0000 至 U+FFFF 以外的字元。

UCS-2?UTF-16?由於 JavaScript 的主要舞台是 Web,開發者難免要面對編碼之類問題,必須對 Unicode 有基本的認識。

字元集是一組符號的集合,字元編碼是字元實際儲存時的位元組格式,如前面的範例看到的,讀取時使用的編碼不正確,編輯器會解讀錯誤而造成亂碼,在還沒有 Unicode 與 UTF(Unicode Transformation Format)前,各個系統間編碼不同而造成的問題,困擾著許多開發者。

要統一編碼問題,必須統一管理符號集合,也就是要有統一的字元集,ISO/IEC 與 Unicode Consortium 兩個團隊都曾經想統一字元集,而 ISO/IEC 在 1990 年先公佈了第一套字元集的編碼方式 UCS-2,使用兩個位元組來編碼字元。

字元集中每個字元會有個編號作為碼點(Code point),實際儲存字元時,UCS-2 以兩個位元組作為一個碼元(Code unit),也就是管理位元組的單位;最初的想法很單純,令碼點與碼元一對一對應,在編碼實作時就可以簡化許多。 後來 1991 年 ISO/IEC 與 Unicode 團隊都認識到,世界不需要兩個不相容的字元集,因而決定合併,之後才發佈了 Unicode 1.0。

由於越來越多的字元被納入 Unicode 字元集,超出碼點 U+0000 至 U+FFFF 可容納的範圍,因而 UCS-2 採用的兩個位元組,無法對應 Unicode 全部的字元碼點,後來在 1996 年公佈了 UTF16。

UTF-16 除了沿用 UCS-2 兩個位元組的編碼部份之外,超出碼點 U+0000 至 U+FFFF 的字元,採用四個位元組來編碼,因而視字元是在哪個碼點範圍,對應的 UTF-16 編碼可能是兩個或四個位元組,也就是說採用 UTF-16 儲存的字元,可能會有一個或兩個碼元。

UTF-16 至少使用兩個位元組,然而對於 +/?@#$ 或者是英文字元等,也使用兩個位元組,感覺蠻浪費儲存空間,而且不相容於已使用 ASCII 編碼儲存的字元,Unicode 的另一編碼標準 UTF-8 用來解決此問題。

UTF-8 儲存字元時使用的位元組數量,也是視字元落在哪個 Unicode 範圍而定,從 UTF-8 的觀點來看,ASCII 編碼是其子集,儲存 ASCII 字元時只使用一個位元組,其他附加符號的拉丁文、希臘文等,會使用兩個位元組(例如 π),至於中文部份,UTF-8 採用三個位元組儲存,更罕見的字元,可能會用到四到六個位元組,例如微笑表情符號 U+1F642,就使用了四個位元組。

簡單來說,Unicode 對字元給予編號以便進行管理,真正要儲存字元時,可以採用 UTF-8、UTF-16 等編碼為位元組。

有些字元無法直接以輸入法鍵入時,JavaScript 可以使用字元在 UTF-16 編碼時的高低位元組來表示,也就是使用兩個碼元,例如高音譜記號的 Unicode 碼點為 U+1D11E,無法直接使用既有的 \uhhhh 來表示,在 ES5 或早期版本,字串必須撰寫為 '\uD834\uDD1E' 來表示,這稱為稱作代理對(Surrogate pair)。

在 ES6,增加了 \u{…} 表示法,要高音譜記號,可以直接撰寫 '\u{1D11E}'

必須留意的是,因為 JavaScript 最初採用 UCS-2,對於原本就存在的 API 或索引,在處理字串時是以碼元為處理單位,支援 UTF-16 後為了兼顧相容性,ECMAScript 規定使用 UTF-16 碼元作為字串的元素(Element)單位 ,而不是把 Unicode 字元作為字串的一個元素。

例如,'林'.length 會是 1,因為使用了一個 UTF-16 碼元,也就是 '\u6797'.length 會是 1;然而,'\uD834\uDD1E'.length 會是 2,雖然它是一個高音譜符號,然而用到了兩個碼元。

布林

布林值只有兩個值,truefalse,分別表示真與假,對布林值使用 typeof,結果會是 'boolean'

複合型態

複合資料型態就是指物件(object),基本上它們都是 Object 的實例,使用 typeof 測試複合資料型態的結果就是 'object'。例如:

> typeof new Object();
'object'
> typeof {};
'object'
> typeof [];
'object'
> 

[] 建立一個陣列,也就是一個 Array 實例,它當然是一個物件,因此 typeof [] 的結果就是 'object'

null 是 JavaScript 中特殊的值,表示沒有任何東西。應用的時機就是在變數不參考至任何物件時,可以指定變數為 null,或測試變數是否參考至 null

> var x = null;
undefined
> x === null;
true
> 

如果你想知道某個物件是哪個型態的實例,可以使用 instanceof 來測試。例如:

> var x1 = {};
undefined
> var x2 = [];
undefined
> x1 instanceof Object;
true
> x2 instanceof Object;
true
> x1 instanceof Array;
false
> x2 instanceof Array;
true
> 

null 使用 typeof 的結果會是 'object',這很怪(語言最初設計沒有處理好的地方吧!只能強記),因為用 instanceof 測試 null 是否為 Object 的實例,結果卻是 false

> typeof null;
'object'
> null instanceof Object;
false
> 

注意!以下的 x 值有參考至一個值,那就是 null,而不是 undefined

var x = null;

以下的 x 才是 undefined

var x;

undefined 是 JavaScript 中特殊的值,當你試圖取得某個沒指定任何值的變數(也沒指定 null)或特性(Properties)時,就會出現 undefined 的結果。對 undefined 使用 typeof 的結果是 'undefined'。在 Node.js 中,undefined 會顯示 undefined。

注意,別與直譯錯誤時出現的「未定義」訊息搞錯了,例如,以下的示範中,並沒有事先宣告 x,因此 x 未定義,會發生 ReferenceError 的錯誤:

> var y = x;
ReferenceError: x is not defined
	at repl:1:9
	at REPLServer.self.eval (repl.js:110:21)
	at repl.js:249:20
	at REPLServer.self.eval (repl.js:122:7)
	at Interface.<anonymous> (repl.js:239:12)
	at Interface.EventEmitter.emit (events.js:95:17)
	at Interface._onLine (readline.js:202:10)
	at Interface._line (readline.js:531:8)
	at Interface._ttyWrite (readline.js:760:14)
	at ReadStream.onkeypress (readline.js:99:10)
> 

如果是以下的程式,是可以正確執行的程式碼,xy 的結果都是 undefined

> var x;
undefined
> var y = x;
undefined
> x
undefined
> y
undefined
> 

undefined 本身等於 undefined

> undefined === undefined;
true
> 

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