[TS手冊學習] 04_類

来源:https://www.cnblogs.com/feixianxing/archive/2023/12/01/typescript-handbook-class-member-method-property-constructor-readonly-visibility-static-this.html
-Advertisement-
Play Games

TS中的類系統對比起JS完善了許多,知識點包括但不限於可訪問性、繼承類、實現介面、訪問器、泛型、抽象類。 ...


TS官方手冊:TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org)

類 Class

類的成員

初始化

類的成員屬性聲明類型:

class Point {
  x: number;
  y: number;
}

類的成員屬性初始化,會在實例化的時候完成賦值:

class Point {
  x: number = 0;
  y: number = 0;
}
嚴格初始化

--strictPropertyInitialization配置項為true的時候,要求成員屬性必須初始化,否則報錯。

可以在聲明成員屬性的時候初始化,也可以在構造函數中初始化。

class GoodGreeter {
    name: string;
    constructor() {
        this.name = "hello";
    }
}

如果打算在構造函數以外初始化欄位,例如依賴一個外部庫來填充類的一部分,則可以使用斷言運算符!來聲明屬性是非空的。

class OKGreeter {
  // 沒有初始化,但不會報錯
  name!: string;
}
只讀 readonly

使用readonly修飾,被readonly修飾的成員只能在構造函數中被賦值(初始化),在其它成員方法中的更新操作會導致錯誤。

class Greeter {
    readonly name: string = "world";

    constructor(otherName?: string) {
        if (otherName !== undefined) {
            this.name = otherName;
        }
    }

    err() {
        // name屬性是只讀的,這裡會導致報錯。
        this.name = "not ok";
    }
}
構造函數
  • 參數列表的類型聲明;

  • 參數的預設值;

  • 構造函數重載:

    class Point {
        // Overloads
        constructor(x: number, y: string);
        constructor(s: string);
        constructor(xs: any, y?: any) {
            // TBD
        }
    }
    

構造函數簽名與函數簽名之間的區別:

  • 構造函數不能使用泛型;
  • 構造函數不能聲明返回值類型。
成員方法

成員方法可以像函數一樣使用類型標註:參數列表的類型與預設值、返回值類型、泛型、重載......

class Point {
    x = 10;
    y = 10;
    scale(n: number): void {
        this.x *= n;
        this.y *= n;
    }
}

:在成員方法中使用成員屬性要通過this,否則可能順著作用域鏈找到類外部的變數。

let x: number = 0;
class C {
    x: string = "hello";

    m() {
        // 這裡的x是第1行的x,類型為number,不能賦值為string,故報錯。
        x = "world";
    }
}
訪問器 getter/setter

在 JS 中,如果沒有需要做數據攔截的需求,是不需要用訪問器的,大可以直接將屬性public暴露到外部。

在 TS 中,訪問器存在如下規則:

  • 如果有getter但沒有setter,那麼屬性是只讀的readonly
  • 如果沒有指定setter方法的value參數類型,那麼則以getter的返回值類型替代;
  • getter和setter的成員可訪問性(public/private/protected)必須一致。
索引簽名

可以為類的實例定義索引簽名,但是很少用,一般將索引數據轉移到別處,例如轉而使用一個對象類型或者數組類型的成員。

類的繼承

和其它面向對象語言一樣,JS 中的類可以從基類中繼承成員屬性和方法。

implements子句(實現介面)
interface Pingable {
    ping(): void;
}
 
class Sonar implements Pingable {
    ping() {
        console.log("ping!");
    }
}

介面只負責聲明成員變數和方法,如果一個類要實現一個介面,則需要實現內部的所有方法。

一個類可以實現多個介面。

註意

  1. 如果介面中聲明瞭函數的類型,在實現該介面的類中仍要聲明類型:
interface Checkable {
    check(name: string): boolean;
}
 
class NameChecker implements Checkable {
    check(s) {
        // 這裡的 s 會被認為是any類型,any類型沒有toLowerCase方法,會報錯
        return s.toLowerCase() === "ok";
    }
}
  1. 當一個類實現一個介面時,這個介面中的可選屬性(optional property)不會被待到類中。
extends子句(繼承基類)
class A extends B{}

其中A被稱為子類或派生類,B是父類或基類。

繼承一個類將繼承它的所有成員屬性和方法。

方法重寫(overriding methods)

可以使用super獲取到父類的方法。

class Base {
    greet() {
        console.log("Hello, world!");
    }
}

class Derived extends Base {
    greet(name?: string) {
        if (name === undefined) {
            super.greet();
        } else {
            console.log(`Hello, ${name.toUpperCase()}`);
        }
    }
}

const d = new Derived();
d.greet();
d.greet("reader");

可以將一個子類的實例賦值給一個父類的實例(實現多態的基礎)。

成員可訪問性 member visibility

public

預設值。使用public修飾的成員可以被任意訪問。

protected

只有這個類和它的子類的成員可以訪問。

子類在修飾繼承自父類的成員可訪問性時,最好帶上protected,否則會預設地變成public,將成員暴露給外部。

class Base {
  protected m = 10;
}
class Derived extends Base {
  // 沒有修飾,預設表示public
  m = 15;
}
const d = new Derived();
console.log(d.m); // 暴露到外部了

跨繼承訪問protected成員

protected的定義就是只有類本身和子類可以訪問。但是在某些面向對象的編程語言中可以通過基類的引用,訪問到非本身且非子類的protected成員。

這種操作在 Java 中被允許,但是在C#、C++、TS 中是非法操作。

原則是:如果D2不是D1的子類,根據protected的定義這種訪問方式就是不合法的。那麼基類跨越這種技巧不能很好的解決問題。當在編碼的過程中遇到這種無法訪問的許可權問題時,應更多地思考類之間的結構設計,而不是採用這種取巧的方式。

image-20231201120318147
class Base {
    protected x: number = 1;
}
class Derived1 extends Base {
    protected x: number = 5;
}
class Derived2 extends Base {
    f1(other: Derived2) {
        other.x = 10;
    }
    f2(other: Derived1) {
        // x被protected修飾,只能被Derived1的子類訪問,但是Derived2不是它的子類,無權訪問,會報錯。
        other.x = 10;
    }
}
private

只有類本身可以訪問。

與protected不同,protected在子類中可訪問,因此可以在子類中進一步開放可訪問性(即改為public)。

但是private修飾的成員無法在子類中訪問,因為無法進一步開放可訪問性。

跨實例訪問private成員

不同的實例只要是由一個類創建,那麼它們就可以相互訪問各自實例上由private修飾的成員。

class A {
    private x = 10;
    public sameAs(other: A) {
        // 不會報錯,因為TS支持跨實例訪問private成員
        return other.x === this.x;
    }
}

大多數面向對象語言支持這種特性,例如:JavaC#C++SwiftPHPTS 也支持。Ruby不支持。

註意事項
  • 成員可訪問性只在TS的類型檢查過程中有效,在最終的 JS 運行時下是無效的,在 JS 運行時下,in操作符和其它獲取對象屬性的方法可以獲取到對象的所有屬性,不管在 TS 中它們是public還是protected還是private修飾的。

  • private屬性支持使用obj.[key]格式訪問,使得單元測試更加方便,但是這種訪問方式執行的是不嚴格的private

    class MySafe {
      private secretKey = 12345;
    }
    const s = new MySafe();
    // 由private修飾的成員無法被訪問,這裡會報錯。
    console.log(s.secretKey);
    // 使用字元串索引訪問,不嚴格,不會報錯。
    console.log(s["secretKey"]);
    

靜態成員

基本特性

靜態成員綁定在類對象上,不需要實例化對象就能訪問。

靜態成員也可以通過publicprotectedprivate修飾可訪問性。

靜態成員也可以被繼承。

靜態成員不能取特殊的變數名,例如:namelengthcall等等。

不要使用Function原型上的屬性作為靜態成員的變數名,會因為衝突而出錯。

靜態代碼塊static block

靜態代碼塊中可以訪問到類內部的所有成員和類外部的內容,通常靜態代碼塊用來初始化類。

class Foo {
    static #count = 0;
    get count() {
        return Foo.#count;
    }
    static {
        try {
            const lastInstances = loadLastInstances();
            Foo.#count += lastInstances.length;
        }
        catch {}
    }
}

泛型類

class Box<Type> {
    contents: Type;
    constructor(value: Type) {
        this.contents = value;
    }
}
const b = new Box("hello!");

泛型類的靜態成員不能引用類型參數。

this 在運行時的指向問題

class MyClass {
    name = "MyClass";
    getName() {
        return this.name;
    }
}
const c = new MyClass();
const obj = {
    name: "obj",
    getName: c.getName,
};

// 這裡會輸出"MyClass"
console.log(c.getName());
// 這裡輸出結果是"obj",而不是"MyClass",因為方法是通過obj調用的。
console.log(obj.getName());

類的方法內部的this預設指向類的實例。但是一旦將方法挑出外部,單獨調用,就很可能報錯。因為函數中的this指向調用該函數的對象,成員方法中的this不一定指向它的實例對象,而是指向實際調用它的對象。

一種解決方法:使用箭頭函數。

箭頭函數中的this指向取決於定義該箭頭函數時所處的上下文,而不是調用時。

class MyClass {
    name = "MyClass";
    getName = () => {
        return this.name;
    };
}
const c = new MyClass();
const g = c.getName;
// 這裡會輸出"MyClass"
console.log(g());

  • 這種解決方案不需要 TS 也能實現;

  • 這種做法會需要更多記憶體,因為箭頭函數不會被放到原型上,每個實例對象都有相互獨立的getName方法;

  • 也因為getName方法沒有在原型鏈上,在這個類的子類中,無法使用super.getName訪問到getName方法。

另一種解決方法:指定this的類型

我們希望this指向實例對象,意味著this的類型應該是MyClass而不能是其他,通過這種類型聲明可以在出錯的時候及時發現。

class MyClass {
    name = "MyClass";
    // 指定this必須是MyClass類型
    getName(this: MyClass) {
        return this.name;
    }
}
const c = new MyClass();
// OK
c.getName();

const g = c.getName;
// Error: 這裡的this會指向undefined或者全局對象。
console.log(g());

  • 每個類定義分配一個函數,而不是每個類實例分配一個函數;
  • 可以使用super調用,因為存在於原型鏈上。

this 類型

在類里存在一種特殊的類型this,表示當前類。

返回值類型為this的情況

class Box {
    contents: string = "";
    // set方法返回了this(這裡的this是對象的引用),因此set方法的返回值類型被推斷為this(這裡的this是類型)
    set(value: string) {
        this.contents = value;
        return this;
    }
}

參數類型為this的情況

class Box {
    content: string = "";
    sameAs(other: this) {
        return other.content === this.content;
    }
}

這種情況下的other:thisother:Box不同,當一個類繼承自Box時,子類中的sameAs方法的this類型將指向子類類型而不是Box

使用this進行類型守護(type guards)

可以在類或介面的方法的返回值類型處使用this is Type,並搭配if語句進行類型收束。

class FileSystemObject {
    isFile(): this is FileRep {
        return this instanceof FileRep;
    }
    isDirectory(): this is Directory {
        return this instanceof Directory;
    }
    isNetworked(): this is Networked & this {
        return this.networked;
    }
	constructor(public path: string, private networked: boolean) {}
}
// 這裡省略了子類的定義...

// 當需要類型收束時:
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
    // 調用isFile方法將返回boolean類型,並且在這個塊內,fso的類型會收束為FileRep
    fso.content;
} else if (fso.isDirectory()) {
    fso.children;
} else if (fso.isNetworked()) {
    fso.host;
}

另外一種常用的情景是:移除undefined類型。

class Box<T> {
    value?: T;
    hasValue(): this is { value: T } {
        return this.value !== undefined;
    }
}
 
const box = new Box();
box.value = "Gameboy";

// (property) Box<unknown>.value?: unknown
box.value;
 
if (box.hasValue()) {
    // (property) value: unknown
    box.value;
}

參數屬性

由於構造函數的參數列表和成員屬性的屬性名大多數時候都是一致的:

class Box{
    private width: number = 0;
    private height: number = 0;
    constructor(width: number, height:number){
        this.width = width;
        this.height = height;
    }
}

TS 支持給類構造函數的參數添加修飾,例如publicprotectedprivatereadonly。只需要在參數列表添加修飾就完成初始化操作,不需要寫構造函數的函數體:

class Params {
    constructor(
    	public readonly x: number,
     	protected y: number,
     	private z: number
    ) {
        // 不需要函數體
    }
}
const a = new Params(1, 2, 3);
console.log(a.x); // 1
console.log(a.z); // Error: z是私有屬性,無法訪問

類表達式

類表達式和類的聲明十分相似,類表達式可以是匿名的,也可以將其賦值給任意標識符並引用它。

const someClass = class<Type> {
    content: Type;
    constructor(value: Type) {
        this.content = value;
    }
};
const m = new someClass("Hello, world");

類表達式實際上是 JS 就有的語法,TS 只是提供了類型標註、泛型等額外的特性。

獲取實例類型

使用InstanceType

class Point {
    createdAt: number;
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.createdAt = Date.now();
        this.x = x;
        this.y = y;
    }
}
// 獲取Point這個類的實例類型
type PointInstance = InstanceType<typeof Point>
 
function moveRight(point: PointInstance) {
    point.x += 5;
}
 
const point = new Point(3, 4);
moveRight(point);
point.x; // => 8

似乎這裡可以直接用point:Point替代point:PointInstance,但是在其它沒有使用class(語法糖)的場景下,InstanceType有以下作用:

image-20231201171526978


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySQL服務配置文件 1、配置文件位置 MySQL服務端的進程mysqld在啟動時,會預設按照以下順序來讀取mysql的配置文件: /etc/my.cnf /etc/mysql/my.cnf 編譯安裝時通過編譯選項指定的位置 如果不想讓mysqld按照這個順序讀取配置文件,可以通過mysqld的 ...
  • ubuntu部署gitlab伺服器 筆者使用的ubuntu版本為20.04,gitlab版本為16.2.1 (此篇文章部分引用他人文件,單純記錄,如有侵權請聯繫) 1、更新命令 cd /home mkdir gitlab cd /gitlab sudo apt update sudo apt-get ...
  • SQL Server中的存儲過程 什麼是存儲過程? 存儲過程是一段預先編寫好的 SQL 代碼,可以保存在資料庫中以供反覆使用。它允許將一系列 SQL 語句組合成一個邏輯單元,併為其分配一個名稱,以便在需要時調用執行。存儲過程可以接受參數,使其更加靈活和通用。 存儲過程語法 創建存儲過程的語法如下: ...
  • 歡迎來到袋鼠雲08期產品功能更新報告!在瞬息萬變的市場環境中,我們深知客戶的需求與期待,因此,我們及時推出袋鼠雲最新產品更新及優化,包括數據治理中心、Hive SQL 性能優化、新插件等,助力企業在數字世界中勇往直前。 以下為袋鼠雲產品功能更新報告08期內容,更多探索,請繼續閱讀。 離線開發平臺 新 ...
  • Apache Paimon是一個流式數據湖平臺。致力於構建一個實時、高效的流式數據湖平臺。這個項目採用了先進的流式計算技術,使企業能夠實時處理和分析大量數據。Apache Paimon 的核心優勢在於它對於大數據生態系統中流式處理的支持,尤其是在高併發和低延遲方面表現出色。 目前業界主流數據湖存儲格 ...
  • 最近聽說好多App都被下架處理了,隱私合規管理特別嚴格。隔壁王老闆公司旗下的一款App就被通報了,說是嵌入的第三方SDK違規收集用戶個人信息。 還記得,在2021年的315晚會,上海、北京有幾家公司都被報道,其SDK均在未經用戶授權,竊取用戶個人信息。涉案App有 50多款,嚴重侵害了用戶權益,播出 ...
  • 前段時間,一個資訊類APP(以下稱“某APP”)的負責人急匆匆找到網安雲,直言其負責的APP最近收到很多用戶投訴,說他們的信息被泄露了,屢遭電銷騷擾。由於電銷太過猖狂,導致很多用戶都到應用市場給他們發差評,對品牌形象塑造和業務發展影響極大! 同時,他們也收到了本地通信管理局的限期整改通知書,責令他們 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 有個朋友說前端技能大家大部分都會,就是部署項目這一塊經驗都比較稀缺,一直很想學一下。所以在這裡寫一篇簡單的從零開始部署前端項目的全過程,感興趣的掘友們或者想自己搭建項目部署的可以看一下這篇。 環境搭建 首先我們需要進行環境搭建主要就 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...