[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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...