<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          TS 類的這10個知識點(diǎn)你掌握了嗎?

          共 11703字,需瀏覽 24分鐘

           ·

          2021-11-27 17:28

          在面向?qū)ο笳Z言中,類是一種面向?qū)ο笥嬎銠C(jī)編程語言的構(gòu)造,是創(chuàng)建對象的藍(lán)圖,描述了所創(chuàng)建的對象共同的屬性和方法。

          一、類的屬性與方法

          1.1 類的成員屬性和靜態(tài)屬性

          在 TypeScript 中,我們可以通過 class 關(guān)鍵字來定義一個類:

          class?Person?{
          ??name:?string;? //?成員屬性
          ??
          ??constructor(name:?string)?{?//?類的構(gòu)造函數(shù)
          ????this.name?=?name;
          ??}
          }

          在以上代碼中,我們使用 class 關(guān)鍵字定義了一個 Person 類,該類含有一個名為 name 的成員屬性。其實 TypeScript 中的類是一個語法糖(所謂的語法糖就是在之前的某個語法的基礎(chǔ)上改變了一種寫法,實現(xiàn)的功能相同,但是寫法不同了,主要是為了讓開發(fā)人員在使用過程中更方便易懂。),若設(shè)置編譯目標(biāo)為 ES5 將會產(chǎn)生以下代碼:

          "use?strict";
          var?Person?=?/**?@class?*/?(function?()?{
          ????function?Person(name)?{
          ????????this.name?=?name;
          ????}
          ????return?Person;
          }());

          類除了可以定義成員屬性外,還可以通過 static 關(guān)鍵字定義靜態(tài)屬性:

          class?Person?{
          ??static?cid:?string?=?"exe";
          ??name:?string;?//?成員屬性
          ??
          ??constructor(name:?string)?{?//?類的構(gòu)造函數(shù)
          ????this.name?=?name;
          ??}
          }

          那么成員屬性與靜態(tài)屬性有什么區(qū)別呢?在回答這個問題之前,我們先來看一下編譯生成的 ES5 代碼:

          "use?strict";
          var?Person?=?/**?@class?*/?(function?()?{
          ????function?Person(name)?{
          ??????this.name?=?name;
          ????}
          ????Person.cid?=?"exe";
          ????return?Person;
          }());

          觀察以上代碼可知,成員屬性是定義在類的實例上,而靜態(tài)屬性是定義在構(gòu)造函數(shù)上。

          1.2 類的成員方法和靜態(tài)方法

          在 TS 類中,我們不僅可以定義成員屬性和靜態(tài)屬性,還可以定義成員方法和靜態(tài)方法,具體如下所示:

          class?Person?{
          ??static?cid:?string?=?"exe";
          ??name:?string;?//?成員屬性
          ??
          ??static?printCid()?{?//?定義靜態(tài)方法
          ????console.log(Person.cid);??
          ??}

          ??constructor(name:?string)?{?//?類的構(gòu)造函數(shù)
          ????this.name?=?name;
          ??}

          ??say(words:?string)?:void?{?//?定義成員方法
          ????console.log(`${this.name}?says:${words}`);??
          ??}
          }

          那么成員方法與靜態(tài)方法有什么區(qū)別呢?同樣,在回答這個問題之前,我們先來看一下編譯生成的 ES5 代碼:

          "use?strict";
          var?Person?=?/**?@class?*/?(function?()?{
          ????function?Person(name)?{
          ????????this.name?=?name;
          ????}
          ????Person.printCid?=?function?()?{
          ????????console.log(Person.cid);
          ????};
          ????Person.prototype.say?=?function?(words)?{
          ????????console.log(this.name?+?"?says\uFF1A"?+?words);
          ????};
          ????Person.cid?=?"exe";
          ????return?Person;
          }());

          由以上代碼可知,成員方法會被添加到構(gòu)造函數(shù)的原型對象上,而靜態(tài)方法會被添加到構(gòu)造函數(shù)上。

          1.3 類成員方法重載

          函數(shù)重載或方法重載是使用相同名稱和不同參數(shù)數(shù)量或類型創(chuàng)建多個方法的一種能力。 在定義類的成員方法時,我們也可以對成員方法進(jìn)行重載:

          class?Person?{
          ??constructor(public?name:?string)?{}

          ??say():?void;?
          ??say(words:?string):?void;
          ??say(words?:?string)?:void?{?//?方法重載
          ????if(typeof?words?===?"string")?{
          ????????console.log(`${this.name}?says:${words}`);??
          ????}?else?{
          ????????console.log(`${this.name}?says:Nothing`);??
          ????}
          ??}
          }

          let?p1?=?new?Person("Semlinker");
          p1.say();
          p1.say("Hello?TS");

          如果想進(jìn)一步了解函數(shù)重載的話,可以繼續(xù)閱讀 是時候表演真正的技術(shù)了 - TS 分身之術(shù) 這一篇文章。

          二、訪問器

          在 TypeScript 中,我們可以通過 gettersetter 方法來實現(xiàn)數(shù)據(jù)的封裝和有效性校驗,防止出現(xiàn)異常數(shù)據(jù)。

          let?passcode?=?"Hello?TypeScript";

          class?Employee?{
          ??private?_fullName:?string?=?"";

          ??get?fullName():?string?{
          ????return?this._fullName;
          ??}

          ??set?fullName(newName:?string)?{
          ????if?(passcode?&&?passcode?==?"Hello?TypeScript")?{
          ??????this._fullName?=?newName;
          ????}?else?{
          ??????console.log("Error:?Unauthorized?update?of?employee!");
          ????}
          ??}
          }

          let?employee?=?new?Employee();
          employee.fullName?=?"Semlinker";

          在以上代碼中,對于私有的 _fullName 屬性,我們通過對外提供 gettersetter 來控制該屬性的訪問和修改。

          三、類的繼承

          繼承(Inheritance)是一種聯(lián)結(jié)類與類的層次模型。指的是一個類(稱為子類、子接口)繼承另外的一個類(稱為父類、父接口)的功能,并可以增加它自己的新功能的能力,繼承是類與類或者接口與接口之間最常見的關(guān)系。通過類的繼承,我們可以實現(xiàn)代碼的復(fù)用。

          繼承是一種 is-a 關(guān)系:

          在 TypeScript 中,我們可以通過 extends 關(guān)鍵字來實現(xiàn)類的繼承:

          3.1 父類

          class?Person?{
          ??constructor(public?name:?string)?{}

          ??public?say(words:?string)?:void?{
          ????console.log(`${this.name}?says:${words}`);??
          ??}
          }

          3.2 子類

          class?Developer?extends?Person?{
          ??constructor(name:?string)?{
          ????super(name);
          ????this.say("Learn?TypeScript")
          ??}
          }

          const?p2?=?new?Developer("semlinker");?
          //?輸出:?"semlinker says:Learn TypeScript"?

          因為 Developer 類繼承了 Person 類,所以我們可以在 Developer 類的構(gòu)造函數(shù)中調(diào)用 say 方法。需要注意的是,在 TypeScript 中使用 extends 時,只能繼承單個類:

          class?Programmer?{}

          //?Classes?can?only?extend?a?single?class.(1174)
          class?Developer?extends?Person,?Programmer?{
          ??constructor(name:?string)?{
          ????super(name);
          ????this.say("Learn?TypeScript")
          ??}
          }

          雖然在 TypeScript 中只允許單繼承,但卻允許我們實現(xiàn)多個接口。具體的使用示例如下所示:

          interface?CanSay?{
          ???say(words:?string)?:void?
          }

          interface?CanWalk?{
          ??walk():?void;
          }

          class?Person?implements?CanSay,?CanWalk?{
          ??constructor(public?name:?string)?{}

          ??public?say(words:?string)?:void?{
          ????console.log(`${this.name}?says:${words}`);??
          ??}

          ??public?walk():?void?{
          ????console.log(`${this.name}?walk?with?feet`);
          ??}
          }

          此外,除了可以繼承具體的實現(xiàn)類之外,在實現(xiàn)繼承時,我們還可以繼承抽象類。

          四、抽象類

          使用 abstract 關(guān)鍵字聲明的類,我們稱之為抽象類。抽象類不能被實例化,因為它里面包含一個或多個抽象方法。 所謂的抽象方法,是指不包含具體實現(xiàn)的方法:

          abstract?class?Person?{
          ??constructor(public?name:?string){}

          ??abstract?say(words:?string)?:void;
          }

          //?Cannot?create?an?instance?of?an?abstract?class.(2511)
          const?lolo?=?new?Person();?//?Error

          抽象類不能被直接實例化,我們只能實例化實現(xiàn)了所有抽象方法的子類。具體如下所示:

          class?Developer?extends?Person?{
          ??constructor(name:?string)?{
          ????super(name);
          ??}
          ??
          ??say(words:?string):?void?{
          ????console.log(`${this.name}?says?${words}`);
          ??}
          }

          const?lolo?=?new?Developer("lolo");
          lolo.say("I?love?ts!");?//?輸出:lolo says I love ts!

          五、類訪問修飾符

          在 TS 類型中,我們可以使用 publicprotectedprivate 來描述該類屬性和方法的可見性。

          5.1 public

          public 修飾的屬性或者方法是公有的,可以在任何地方被訪問到,默認(rèn)所有的屬性或者方法都是 public:

          class?Person?{
          ??constructor(public?name:?string)?{}

          ??public?say(words:?string)?:void?{
          ????console.log(`${this.name}?says:${words}`);??
          ??}
          }

          5.2 protected

          protected 修飾的屬性或者方法是受保護(hù)的,它和 private 類似,不同的地方是 protected 成員在派生類中仍然可以訪問。

          class?Person?{
          ??constructor(public?name:?string)?{}

          ??public?say(words:?string)?:void?{
          ????console.log(`${this.name}?says:${words}`);??
          ??}

          ??protected?getClassName()?{
          ????return?"Person";
          ??}
          }

          const?p1?=?new?Person("lolo");
          p1.say("Learn?TypeScript");?//?Ok
          //?Property?'getClassName'?is?protected?and?only?accessible?within?class?'Person'?and?its?subclasses.
          p1.getClassName()?//?Error

          由以上錯誤信息可知,使用 protected 修飾符修飾的方法,只能在當(dāng)前類或它的子類中使用。

          class?Developer?extends?Person?{
          ??constructor(name:?string)?{
          ????super(name);
          ????console.log(`Base Class:${this.getClassName()}`);
          ??}
          }

          const?p2?=?new?Developer("semlinker");?//?輸出:"Base Class:Person"?

          5.3 private

          private 修飾的屬性或者方法是私有的,只能在類的內(nèi)部進(jìn)行訪問。

          class?Person?{
          ??constructor(private?id:?number,?public?name:?string)?{}
          }

          const?p1?=?new?Person(28,?"lolo");
          //?Property?'id'?is?private?and?only?accessible?within?class?'Person'.(2341)
          p1.id?//?Error
          p1.name?//?OK

          由以上錯誤信息可知,使用 private 修飾符修飾的屬性,只能在當(dāng)前類內(nèi)部訪問。但真的是這樣么?其實這只是 TS 類型檢查器給我們的提示,在運(yùn)行時我們還是可以訪問 Person 實例的 id 屬性。不相信的話,我們來看一下編譯生成的 ES5 代碼:

          "use?strict";
          var?Person?=?/**?@class?*/?(function?()?{
          ????function?Person(id,?name)?{
          ??????this.id?=?id;
          ??????this.name?=?name;
          ????}
          ????return?Person;
          }());
          var?p1?=?new?Person(28,?"lolo");

          5.4 私有字段

          針對上面的問題,TypeScript 團(tuán)隊在 3.8 版本就開始支持 ECMAScript 私有字段,使用方式如下:

          class?Person?{
          ??#name:?string;

          ??constructor(name:?string)?{
          ????this.#name?=?name;
          ??}
          }

          let?semlinker?=?new?Person("semlinker");
          //?Property?'#name'?is?not?accessible?outside?class?'Person'?because?it?has?a?private?identifier.
          semlinker.#name?//?Error

          那么 ECMAScript 私有字段private 修飾符相比,有什么特別之處么?這里我們來看一下編譯生成的 ES2015 代碼:

          "use?strict";
          var?__classPrivateFieldSet?=?//?省略相關(guān)代碼
          var?_Person_name;
          class?Person?{
          ??constructor(name)?{
          ????_Person_name.set(this,?void?0);
          ????__classPrivateFieldSet(this,?_Person_name,?name,?"f");
          ??}
          }

          _Person_name?=?new?WeakMap();
          let?semlinker?=?new?Person("Semlinker");

          觀察以上的結(jié)果可知,在處理私有字段時使用到了 ES2015 新增的?WeakMap 數(shù)據(jù)類型,如果你對 WeakMap 還不了解的話,可以閱讀 你不知道的 WeakMap 這篇文章。下面我們來總結(jié)一下,私有字段與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同之處:

          • 私有字段以 # 字符開頭,有時我們稱之為私有名稱;
          • 每個私有字段名稱都唯一地限定于其包含的類;
          • 不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private);
          • 私有字段不能在包含的類之外訪問,甚至不能被檢測到。

          六、類表達(dá)式

          TypeScript 1.6 添加了對 ES6 類表達(dá)式的支持。類表達(dá)式是用來定義類的一種語法。和函數(shù)表達(dá)式相同的一點(diǎn)是,類表達(dá)式可以是命名也可以是匿名的。如果是命名類表達(dá)式,這個名字只能在類體內(nèi)部才能訪問到。

          類表達(dá)式的語法如下所示([] 方括號表示是可選的):

          const?MyClass?=?class?[className]?[extends]?{
          ??//?class?body
          };

          基于類表達(dá)式的語法,我們可以定義一個 Point 類:

          let?Point?=?class?{
          ??constructor(public?x:?number,?public?y:?number)?{}
          ??public?length()?{
          ????return?Math.sqrt(this.x?*?this.x?+?this.y?*?this.y);
          ??}
          }

          let?p?=?new?Point(3,?4);
          console.log(p.length());?//?輸出:5

          需要注意在使用類表達(dá)式定義類的時候,我們也可以使用 extends 關(guān)鍵字。篇幅有限,這里就不展開介紹了,感興趣的小伙伴可以自行測試一下。

          七、泛型類

          在類中使用泛型也很簡單,我們只需要在類名后面,使用 的語法定義任意多個類型變量,具體示例如下:

          class?Person?{
          ??constructor(
          ????public?cid:?T,?
          ????public?name:?string
          ??
          )?{}???
          }

          let?p1?=?new?Person<number>(28,?"Lolo");
          let?p2?=?new?Person<string>("exe",?"Semlinker");

          接下來我們以實例化 p1 為例,來分析一下其處理過程:

          • 在實例化 Person 對象時,我們傳入 number 類型和相應(yīng)的構(gòu)造參數(shù);
          • 之后在 Person 類中,類型變量 T 的值變成 number 類型;
          • 最后構(gòu)造函數(shù) cid 的參數(shù)類型也會變成 number 類型。

          相信看到這里一些讀者會有疑問,我們什么時候需要使用泛型呢?通常在決定是否使用泛型時,我們有以下兩個參考標(biāo)準(zhǔn):

          • 當(dāng)你的函數(shù)、接口或類將處理多種數(shù)據(jù)類型時;
          • 當(dāng)函數(shù)、接口或類在多個地方使用該數(shù)據(jù)類型時。

          八、構(gòu)造簽名

          在 TypeScript 接口中,你可以使用 new 關(guān)鍵字來描述一個構(gòu)造函數(shù):

          interface?Point?{
          ??new?(x:?number,?y:?number):?Point;
          }

          以上接口中的 new (x: number, y: number) 我們稱之為構(gòu)造簽名,其語法如下:

          ConstructSignature:new?TypeParametersopt?(?ParameterListopt?)?TypeAnnotationopt

          在上述的構(gòu)造簽名中,TypeParametersoptParameterListoptTypeAnnotationopt 分別表示:可選的類型參數(shù)、可選的參數(shù)列表和可選的類型注解。那么了解構(gòu)造簽名有什么用呢?這里我們先來看個例子:

          interface?Point?{
          ??new?(x:?number,?y:?number):?Point;
          ??x:?number;
          ??y:?number;
          }

          class?Point2D?implements?Point?{
          ??readonly?x:?number;
          ??readonly?y:?number;

          ??constructor(x:?number,?y:?number)?{
          ????this.x?=?x;
          ????this.y?=?y;
          ??}
          }

          const?point:?Point?=?new?Point2D(1,?2);?//?Error

          對于以上的代碼,TypeScript 編譯器(v4.4.3)會提示以下錯誤信息:

          Type?'Point2D'?is?not?assignable?to?type?'Point'.
          Type?'Point2D'?provides?no?match?for?the?signature?'new?(x:?number,?y:?number):?Point'.

          要解決這個問題,我們就需要把對前面定義的 Point 接口進(jìn)行分離:

          interface?Point?{
          ??x:?number;
          ??y:?number;
          }

          interface?PointConstructor?{
          ??new?(x:?number,?y:?number):?Point;
          }

          完成接口拆分之后,除了前面已經(jīng)定義的 Point2D 類之外,我們又定義了一個 newPoint 工廠函數(shù),該函數(shù)用于根據(jù)傳入的 PointConstructor 類型的構(gòu)造函數(shù),來創(chuàng)建對應(yīng)的 Point 對象。

          class?Point2D?implements?Point?{
          ??readonly?x:?number;
          ??readonly?y:?number;

          ??constructor(x:?number,?y:?number)?{
          ????this.x?=?x;
          ????this.y?=?y;
          ??}
          }

          function?newPoint( // 工廠方法
          ??pointConstructor:?PointConstructor,
          ??x:?number,
          ??y:?number
          ):?Point?
          {
          ??return?new?pointConstructor(x,?y);
          }

          const?point:?Point?=?newPoint(Point2D,?1,?2);

          九、抽象構(gòu)造簽名

          在 TypeScript 4.2 版本中引入了抽象構(gòu)造簽名,用于解決以下的問題:

          type?ConstructorFunction?=?new?(...args:?any[])?=>?any;

          abstract?class?Utilities?{}

          //?Type?'typeof?Utilities'?is?not?assignable?to?type?'ConstructorFunction'.
          //?Cannot?assign?an?abstract?constructor?type?to?a?non-abstract?constructor?type.
          let?UtilityClass:?ConstructorFunction?=?Utilities;?//?Error.

          由以上的錯誤信息可知,我們不能把抽象構(gòu)造器類型分配給非抽象的構(gòu)造器類型。針對這個問題,我們需要使用 abstract 修飾符:

          declare?type?ConstructorFunction?=?abstract?new?(...args:?any[])?=>?any;

          需要注意的是,對于抽象構(gòu)造器類型,我們也可以傳入具體的實現(xiàn)類:

          declare?type?ConstructorFunction?=?abstract?new?(...args:?any[])?=>?any;

          abstract?class?Utilities?{}
          class?UtilitiesConcrete?extends?Utilities?{}

          let?UtilityClass:?ConstructorFunction?=?Utilities;?//?Ok
          let?UtilityClass1:?ConstructorFunction?=?UtilitiesConcrete;?//?Ok

          而對于 TypeScript 4.2 以下的版本,我們可以通過以下方式來解決上面的問題:

          type?Constructor?=?Function?&?{?prototype:?T?}

          abstract?class?Utilities?{}

          class?UtilitiesConcrete?extends?Utilities?{}

          let?UtilityClass:?Constructor?=?Utilities;
          let?UtilityClass1:?Constructor?=?UtilitiesConcrete;

          介紹完抽象構(gòu)造簽名,最后我們來簡單介紹一下 class type 與 typeof class type 的區(qū)別。

          十、class type 與 typeof class type

          class?Person?{
          ??static?cid:?string?=?"exe";
          ??name:?string;?//?成員屬性
          ??
          ??static?printCid()?{?//?定義靜態(tài)方法
          ????console.log(Person.cid);??
          ??}

          ??constructor(name:?string)?{?//?類的構(gòu)造函數(shù)
          ????this.name?=?name;
          ??}

          ??say(words:?string)?:void?{?//?定義成員方法
          ????console.log(`${this.name}?says:${words}`);??
          ??}
          }

          //?Property?'say'?is?missing?in?type?'typeof?Person'?but?required?in?type?'Person'.
          let?p1:?Person?=?Person;?//?Error
          let?p2:?Person?=?new?Person("Semlinker");?//?Ok

          //?Type?'Person'?is?missing?the?following?properties?from?type?'typeof?Person':?prototype,?cid,?printCid
          let?p3:?typeof?Person?=?new?Person("Lolo");?//?Error
          let?p4:?typeof?Person?=?Person;?//?Ok

          通過觀察以上的代碼,我們可以得出以下結(jié)論:

          • 當(dāng)使用 Person 類作為類型時,可以約束變量的值必須為 Person 類的實例;
          • 當(dāng)使用 typeof Person 作為類型時,可以約束變量的值必須包含該類上的靜態(tài)屬性和方法。

          此外,需要注意的是 TypeScript 使用的是 結(jié)構(gòu)化 類型系統(tǒng),與 Java/C++ 所采用的 名義化 類型系統(tǒng)是不一樣的,所以以下代碼在 TS 中是可以正常運(yùn)行的:

          class?Person?{
          ??constructor(public?name:?string)?{}??
          }

          class?SuperMan?{
          ??constructor(public?name:?string)?{}??
          }

          let?p1:?SuperMan?=?new?Person("Semlinker");?//?Ok

          好的,在日常工作中,TypeScript 類比較常見的知識,就介紹到這里,感謝阿寶哥的分享!

          十一、參考資源

          • TypeScript 1.6
          • how-to-use-classes-in-typescript




          瀏覽 47
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  伊伊成人网 | 欧美性爱 在线 | 欧洲成人在线视频 | 免费黄色电影在线网站 | 国产一级A片免费在线观看 |