第一週第二天:原始資料型別與任意值

第一週第二天:原始資料型別與任意值

學習資源


JavaScript 的資料型別(Data types)一共有六種基本(Primitive)資料型別與物件(Object)。

基本資料型別包括:boolean(布林值)、number(數字)、string(字串)、null(無值)、undefined(未定義值)與 symbol (標識符,ES6)。

以下各型別的範例程式碼將會根據我的個人命名喜好進行調整,因此與書中的內容會有所不同。請依照喜歡的範例進行學習即可。

原始資料型別

布林值

在 TypeScript 中,用 boolean 來表示布林值:

let isActive: boolean = true;

function isEven(n: number): boolean {
  return n % 2 === 0;
}

要注意的是,使用建構函式 Boolean 建立的是包裝了布林值的物件而不是布林值。其他除了 nullundefined 的基本型別(如 numberstring)也都有對應的建構函式(NumberString),它們的功能類似於 Boolean 建構函式,後面不會重複說明此部份。

// 🔴
const isNewBoolean: boolean = new Boolean(false);
// error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
//  'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.

// 🟢
const boolObj: Boolean = new Boolean(false);

數字

在 TypeScript 中,用 number 來表示數字:

(註:書中原本的變數命名部分使用縮寫,我不太理解原意,因此請 ChatGPT 將變數重新命名並加上註解,使其代表意義更清楚明確)

// 十進位
let decimalNumber: number = 6;

// 十六進位
let hexadecimalNumber: number = 0xf00d;

// 二進位 (ES6 中的二進位制表示法)
let binaryNumber: number = 0b1010;

// 八進位 (ES6 中的八進位制表示法)
let octalNumber: number = 0o744;

// NaN (Not a Number) 值,表示非數字結果
let notANumberValue: number = NaN;

// 無窮大數字值
let infinityValue: number = Infinity;

編譯成 JavaScript 的結果:

var decimalNumber = 6;
var hexadecimalNumber = 0xf00d;

var binaryNumber = 10; // 注意,編譯後的二進位數字將被轉換為十進位數字
var octalNumber = 484; // 注意,編譯後的八進位數字將被轉換為十進位數字

var notANumberValue = NaN;
var infinityValue = Infinity;

字串

在 TypeScript 中,用 string 來表示字串:

let name: string = "John";

function greet(person: string): string {
  return `Hello, ${person}!`;
}

編譯成 JavaScript 的結果:

var name = "John";

function greet(person) {
    return "Hello, " + person + "!";
}

無值與未定義值

在 TypeScript 中,nullundefined 是特殊的子型別,可以賦值給其他型別(例如,stringnumber)的變數(除非啟用 strictNullChecks 選項):

let undefinedValue: undefined = undefined;
let nullValue: null = null;

// 這些都不會報錯
let x: number = null;
let y: string = undefined;

在啟用 strictNullChecks 選項時,需要明確地指定變數可以為 nullundefined

let x: number | null = null;
let y: string | undefined = undefined;

「子型別」什麼是意思?

ChatGPT:「子型別(Subtype)是一個術語,用於描述型別系統中的一種關係。當一個型別 A 是另一個型別 B 的子型別時,意味著 A 的實例可以替代 B 的實例,而不會導致程式出錯或不正確的行為。在物件導向編程中,子型別通常與繼承和多態性有關。」

嗯~有看沒有懂,只好變身十歲小孩再問一次。它第一次是用貓狗都是動物來解釋,但我還是聽不懂(就不貼上來了),第二次它改用不同顏色的球來比喻。

ChatGPT:『想像一下,你有一個大盒子,裡面裝滿了各種顏色的小球。這個大盒子就代表「所有顏色的球」類別。現在,我們可以在裡面找到紅色的球、藍色的球和綠色的球等等。這些特定顏色的球都是「所有顏色的球」這個大類別的一部分,因為它們都是球。

所以,在這個例子中,「紅色的球」、「藍色的球」和「綠色的球」等等,都是「所有顏色的球」的子型別。這是因為它們都具有「球」的基本特徵,但同時也具有自己獨特的顏色。

子型別的概念就是讓我們可以在需要的時候,把特定顏色的球當作「所有顏色的球」來看待和使用。因為無論顏色如何,它們都是球,所以可以互相替換使用。』

到這裡我有點誤解子型別的意思,會發現是因為我把顏色替換成五個基本型別標籤,問 ChatGPT 這樣的理解正不正確。我以為 nullundefined 是其他基本型別的子型別,經過 ChatGPT 的解釋(見下方),才恍然大悟為什麼會說 nullundefined 是「特殊的」子型別。

「子型別通常在物件導向編程中,涉及到繼承和多態性。然而,booleanstringnumbernullundefined 這些基本型別是 TypeScript 中最基本的數據類型,它們之間沒有繼承或子型別的關係。」

「在 TypeScript 中,nullundefined 有時可以被賦予其他基本型別的變量,這是因為在預設情況下,TypeScript 允許 nullundefined 賦值給其他所有型別。然而,這並不意味著 nullundefined 是其他基本型別的子型別。」

如何啟用 strictNullChecks 選項?

tsconfig.jsonstrictNullChecks 選項設定為 true

{
  "compilerOptions": {
    "strictNullChecks": true,
    // 其他編譯選項...
  }
}

特殊型別

無返回值

(註:這裡沒有跟書中一樣用「空值」來說明,是為了避免自己把 voidnull 搞錯)

在 TypeScript 中,void 是一個特殊型別,用來表示一個函式沒有返回值:

function printMessage(message: string): void {
  console.log(message);
}

void 型別作為變數的型別沒有太大的用處,因為該變數只能賦值為 nullundefined 這兩個特殊的子型別:

let voidVariable: void = undefined;

「所以可以理解 void 跟其他基本型別是平行的囉?」我再次向 ChatGPT 確認自己的理解。

ChatGPT:「是的,您可以將 void 視為與其他基本型別(如 booleanstringnumbernullundefined)平行的一個型別。在 TypeScript 中,void 是一個獨立的基本型別,主要用於表示一個函式沒有返回值。」

任意值

在 TypeScript 中,any 是一個特殊型別,用來表示允許將變數標記為可以接受任何型別的值:

let anything: any = "Hello";
anything = 42;
anything = [1, 2, 3];
anything.foo = "bar";
anything();

(OS:看來 any 是 TypeScript 界的任意門)

對比基本型別,當賦值型別與定義的不相同,型別系統是會逼逼叫的(還記得昨天的解釋嗎?想不起來可以回到昨天的筆記複習喔!):

let myLuckyNumber: number = 7;
myLuckyNumber = "7";
// error TS2322: Type 'string' is not assignable to type 'number'.

另外,如果變數被標記為任意值 any 的型別,就算存取不存在的屬性或呼叫不存在的方法,通通給過不會報錯,根本開大絕!

let someObject: any = { name: "John" };
console.log(someObject.age); // 不會報錯,即使屬性 'age' 不存在
let someValue: any = "Hello";
someValue.unknownMethod(); // 不會報錯,即使方法 'unknownMethod' 不存在

當我們對 any 型別的變數進行操作時,返回的值也會被視為 any 型別。表示可以將返回值再賦值給任何其他型別的變數,或者對返回值進行任何操作,也都不會報錯。

請注意,關於「未宣告型別的變數」這段描述「變數如果在宣告的時候,未指定其型別,那麼它會被識別為任意值型別」並不十分精確。

更準確地說是「變數如果在宣告的時候,未指定其型別且沒有初始值時,那麼它會被識別為任意值型別」,詳細原因請見下方說明。

還記得昨天有提到「 TypeScript 會透過『型別推論』的規則來推斷其型別」,那它靠什麼來推斷呢?「初始值」。反之,若是變數沒有給初始值時,TypeScript 就只能推空氣,像空氣般的型別我想就是任意值 any 了。


補充:本來是用「初始化的值」與「若是變數沒有初始化」,但之後想起用 var 宣告變數其實是會自動初始化為 undefined 的,因此將用詞改為「初始值」。


接下來我會放上 ChatGPT 給的範例來做說明。

以下是一個未指定型別的變數宣告範例:

let someVariable = "Hello, World!";

在此範例中,因為變數 someVariable 初始值為一個字串,TypeScript 編譯器會推斷其型別為 string。如果初始值是數字、布林值等,編譯器會相應地推斷出其他型別。

然而,如果變數在宣告時沒有給初始值,TypeScript 編譯器無法推斷出變數的型別,因此它將使用 any 型別:

let someVariable; // 等同於 let someVariable: any;

在這種情況下,變數 someVariable 的型別將被設為 any,這意味著可以將任何型別的值賦給它,而編譯器不會報告任何錯誤。

讀到這我不禁心想:用任意值難道不就跟寫 JavaScript 一樣,門戶大開,那我這麼辛苦學 TypeScript 幹嘛?

不過當我回想起之前重構時,看著那一大堆的紅色錯誤提示,真的有股衝動想直接 any 催下去,假裝什麼都沒看見,都是我眼睛業障重。

瞬間了然於心,任意值還是有存在的必要性。(合掌)