--- GA: G-7BSJTG7RYN --- **封裝**在類別中的主要目的,是為了控制類別中的成員(變數/屬性或函式等)被**存取**的**權限**。 一般我們會在宣告變數/屬性或函式的最前方加上 **存取修飾子**: ```typescript= class Counter { private count: number = 0; } ``` 語法中的 `private` 就是[存取修飾子](https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility)。 ### 存取修飾子 主要:`public`, `protected`, `private` 其他:`static`, `abstract` - 在 TypeScript 中若沒有特別聲明任何修飾子,預設會是 `public` 。 - `static`, `abstract` 可與 `public`, `protected`, `private` 同時存在。 -- #### Public 在 `class` 內外、及 **繼承的子類別** 中,皆可任意存取聲明為 `public` 的成員: ```typescript= class Rectangle { public width: number = 100; // 建構式 constructor() { this.width = 50; } changeVal(){ // 類別內的函式:可任意存取更改 `width` this.width = 80; } } const square = new Rectangle(); square.width = 40; // 類別外:可任意存取更改 `width` console.log(square.width); ``` 也就是聲明為 `public` 的成員,不會有任何存取限制。 此處聲明 `width` 的修飾子 `public` 可省略。 #### Protected 只可在 **同個類別** 中,或是 **繼承的子類別** 中存取。 ```typescript= class Rectangle { protected width: number = 100; // 建構式 constructor() { this.width = 50; } changeVal(){ this.width = 80; console.log(this.width) } } class Triangle extends Rectangle { showVal() { this.width = 120; console.log(this.width); } } const triangle = new Triangle(); triangle.showVal(); // 120 const square = new Rectangle(); square.changeVal(); // 80 console.log(square.width); // error ``` 從最後一行可以發現,在類別外想直接存取 `width` ,就會因為 `protected` 的權限保護而產生錯誤訊息: > Property 'width' is protected and only accessible within class 'Rectangle' and its subclasses. #### Private 只可在 **同個類別** 中存取。 我們直接用 `protected` 的範例來看: ```typescript= class Rectangle { private width: number = 100; // 建構式 constructor() { this.width = 50; } changeVal(){ this.width = 80; console.log(this.width) } } class Triangle extends Rectangle { showVal() { this.width = 120; // error } } const triangle = new Triangle(); triangle.showVal(); // 120 const square = new Rectangle(); console.log(square.width); // error ``` 從這段程式你會發現,只要不是在該類別中存取聲明為 `private` 的屬性,都會顯示錯誤訊息: > Property 'width' is private and only accessible within class 'Rectangle'. #### static 聲明為 `static` 的靜態成員,不需要透過創建物件實體,就能直接存取與更改。 ```typescript= class Rectangle { static width: number = 100; } Rectangle.width = 50; console.log(Rectangle.width); // 50 ``` - `static` 可與 `public`, `protected`, `private` 同時存在 - 權限規則與 `public`, `protected`, `private` 相同 - 存取方式改變,由 `this.property` 改為 `ClassName.property` 以 `public` 為例,權限與 `public` 相同,任何地方都可存取 `width`: ```typescript= class Rectangle { public static width: number = 100; // 建構式 constructor() { Rectangle.width = 50; } changeVal(){ Rectangle.width = 80; console.log(Rectangle.width) } } const square = new Rectangle(); square.changeVal(); // 80 ``` 看出差異了嗎? 一但使用了 `static` ,存取方式就會由 `this.width` 改為 `Rectangle.width`。 而且不管前方的修飾子使用 `public`, `protected` 或 `private`,存取方式都是 `Rectangle.width`。 再進一步來看,若我們將函式也加上 `static` 修飾子呢: ```typescript= class Rectangle { public static width: number = 100; // 建構式 constructor() { Rectangle.width = 50; } static changeVal(){ Rectangle.width = 80; console.log(Rectangle.width) } } // const square = new Rectangle(); // no need Rectangle.changeVal(); // ★ ``` 最後一行,我們的存取方式也從 `square.changeVal()` 變成了 `Rectangle.changeVal()`,也不再需要先使用 `new` 創建物件。 看起來很不錯,可以減少創建的步驟。 不過聲明為 `static` 的成員,**無法繼承或覆寫**,而過度使用靜態成員,也會降低可讀性和可維護性。 -- #### Abstract 我們直接用 `abstract` 來宣告類別與方法: ```typescript= abstract class Rectangle { public width: number = 100; // 建構式 constructor() { this.width = 50; } public abstract changeVal(){ // ERROR this.width = 80; } } const square = new Rectangle(); // ERROR square.changeVal(); ``` 錯誤發生原因發生原因發生原 1. 定義為 `abstract` 的類別,不可使用 `new` 關鍵字創建。 2. 定義為 `abstract` 的方法(函式),不可實作任何內容,也就是函式只能宣告,不能撰寫或回傳任何內容。 那麼要怎麼使用定義為 `abstract` 的方法呢? 我們必須先瞭解到,被定義為 `abstract` 的方法,只能被子類別實作(implementation)。 所以同一段語法我們來看看該怎麼撰寫: ```typescript= abstract class Rectangle { public width: number = 100; // 建構式 constructor() { this.width = 50; } public abstract changeVal(): void; } // 子類別 class Square extends Rectangle { public changeVal() { this.width = 80; console.log(this.width); } } let shape = new Square(); shape.changeVal(); // 80 ``` 在定義為 `abstract` 的方法,我們將型別註記為 `void`,而內容實作則是在子類別 `Square` 中來處理。 --- ### Reference - [TypeScript Official Site](https://www.typescriptlang.org/docs/) - [Classes (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) --- > 系列:[`跑完 JS30 就接著認識 TypeScript 入門`](https://hackmd.io/@elzuoc?tags=%5B%22%E8%B7%91%E5%AE%8C+JS30+%E5%B0%B1%E6%8E%A5%E8%91%97%E8%AA%8D%E8%AD%98+TypeScript+%E5%85%A5%E9%96%80%22%5D) > 上一篇:[Day10:認識類別 Class](https://hackmd.io/@elzuoc/HkSlRVueh) > 下一篇:[Day12:認識類別 Class - 封裝 Encapsulation -- Getter & Setter](https://hackmd.io/@elzuoc/S1EMtCRgn) ###### tags: [`跑完 JS30 就接著認識 TypeScript 入門`](https://hackmd.io/@elzuoc?tags=%5B%22%E8%B7%91%E5%AE%8C+JS30+%E5%B0%B1%E6%8E%A5%E8%91%97%E8%AA%8D%E8%AD%98+TypeScript+%E5%85%A5%E9%96%80%22%5D) ###### Created on ∥ March 27, 2023 ###### 文章若有任何錯誤,還請不吝給予留言指正,謝謝大家!