---
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
###### 文章若有任何錯誤,還請不吝給予留言指正,謝謝大家!