<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】1010- 細(xì)數(shù) 10 個(gè) TypeScript 奇怪的符號(hào)

          共 32055字,需瀏覽 65分鐘

           ·

          2021-07-10 00:24


          TypeScript 是一種由微軟開發(fā)的自由和開源的編程語言。它是 JavaScript 的一個(gè)超集,而且本質(zhì)上向這個(gè)語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?/p>

          本文阿寶哥將分享這些年在學(xué)習(xí) TypeScript 過程中,遇到的 10 大 “奇怪” 的符號(hào)。其中有一些符號(hào),阿寶哥第一次見的時(shí)候也覺得 “一臉懵逼”,希望本文對(duì)學(xué)習(xí) TypeScript 的小伙伴能有一些幫助。

          好的,下面我們來開始介紹第一個(gè)符號(hào) —— ! 非空斷言操作符。

          一、! 非空斷言操作符

          在上下文中當(dāng)類型檢查器無法斷定類型時(shí),一個(gè)新的后綴表達(dá)式操作符 ! 可以用于斷言操作對(duì)象是非 null 和非 undefined 類型。具體而言,x! 將從 x 值域中排除 null 和 undefined 。

          那么非空斷言操作符到底有什么用呢?下面我們先來看一下非空斷言操作符的一些使用場(chǎng)景。

          1.1 忽略 undefined 和 null 類型

          function myFunc(maybeString: string | undefined | null{
            // Type 'string | null | undefined' is not assignable to type 'string'.
            // Type 'undefined' is not assignable to type 'string'. 
            const onlyString: string = maybeString; // Error
            const ignoreUndefinedAndNull: string = maybeString!; // Ok
          }

          1.2 調(diào)用函數(shù)時(shí)忽略 undefined 類型

          type NumGenerator = () => number;

          function myFunc(numGenerator: NumGenerator | undefined{
            // Object is possibly 'undefined'.(2532)
            // Cannot invoke an object which is possibly 'undefined'.(2722)
            const num1 = numGenerator(); // Error
            const num2 = numGenerator!(); //OK
          }

          因?yàn)?! 非空斷言操作符會(huì)從編譯生成的 JavaScript 代碼中移除,所以在實(shí)際使用的過程中,要特別注意。比如下面這個(gè)例子:

          const a: number | undefined = undefined;
          const b: number = a!;
          console.log(b); 

          以上 TS 代碼會(huì)編譯生成以下 ES5 代碼:

          "use strict";
          const a = undefined;
          const b = a;
          console.log(b);

          雖然在 TS 代碼中,我們使用了非空斷言,使得 const b: number = a!; 語句可以通過 TypeScript 類型檢查器的檢查。但在生成的 ES5 代碼中,! 非空斷言操作符被移除了,所以在瀏覽器中執(zhí)行以上代碼,在控制臺(tái)會(huì)輸出 undefined。

          ??  繼續(xù)閱讀介紹了 ?. 和 ?? 運(yùn)算符,再來個(gè) ! 非空斷言操作符

          二、?. 運(yùn)算符

          TypeScript 3.7 實(shí)現(xiàn)了呼聲最高的 ECMAScript 功能之一:可選鏈(Optional Chaining)。有了可選鏈后,我們編寫代碼時(shí)如果遇到 nullundefined 就可以立即停止某些表達(dá)式的運(yùn)行??蛇x鏈的核心是新的 ?. 運(yùn)算符,它支持以下語法:

          obj?.prop
          obj?.[expr]
          arr?.[index]
          func?.(args)

          這里我們來舉一個(gè)可選的屬性訪問的例子:

          const val = a?.b;

          為了更好的理解可選鏈,我們來看一下該 const val = a?.b 語句編譯生成的 ES5 代碼:

          var val = a === null || a === void 0 ? void 0 : a.b;

          上述的代碼會(huì)自動(dòng)檢查對(duì)象 a 是否為 nullundefined,如果是的話就立即返回 undefined,這樣就可以立即停止某些表達(dá)式的運(yùn)行。你可能已經(jīng)想到可以使用 ?. 來替代很多使用 && 執(zhí)行空檢查的代碼:

          if(a && a.b) { } 

          if(a?.b){ }
          /**
          * if(a?.b){ } 編譯后的ES5代碼

          * if(
          *  a === null || a === void 0 
          *  ? void 0 : a.b) {
          * }
          */

          但需要注意的是,?.&& 運(yùn)算符行為略有不同,&& 專門用于檢測(cè) falsy 值,比如空字符串、0、NaN、null 和 false 等。而 ?. 只會(huì)驗(yàn)證對(duì)象是否為 nullundefined,對(duì)于 0 或空字符串來說,并不會(huì)出現(xiàn) “短路”。

          2.1 可選元素訪問

          可選鏈除了支持可選屬性的訪問之外,它還支持可選元素的訪問,它的行為類似于可選屬性的訪問,只是可選元素的訪問允許我們?cè)L問非標(biāo)識(shí)符的屬性,比如任意字符串、數(shù)字索引和 Symbol:

          function tryGetArrayElement<T>(arr?: T[], index: number = 0{
            return arr?.[index];
          }

          以上代碼經(jīng)過編譯后會(huì)生成以下 ES5 代碼:

          "use strict";
          function tryGetArrayElement(arr, index{
              if (index === void 0) { index = 0; }
              return arr === null || arr === void 0 ? void 0 : arr[index];
          }

          通過觀察生成的 ES5 代碼,很明顯在 tryGetArrayElement 方法中會(huì)自動(dòng)檢測(cè)輸入?yún)?shù) arr 的值是否為 nullundefined,從而保證了我們代碼的健壯性。

          2.2 可選鏈與函數(shù)調(diào)用

          當(dāng)嘗試調(diào)用一個(gè)可能不存在的方法時(shí)也可以使用可選鏈。在實(shí)際開發(fā)過程中,這是很有用的。系統(tǒng)中某個(gè)方法不可用,有可能是由于版本不一致或者用戶設(shè)備兼容性問題導(dǎo)致的。函數(shù)調(diào)用時(shí)如果被調(diào)用的方法不存在,使用可選鏈可以使表達(dá)式自動(dòng)返回 undefined 而不是拋出一個(gè)異常。

          可選調(diào)用使用起來也很簡(jiǎn)單,比如:

          let result = obj.customMethod?.();

          該 TypeScript 代碼編譯生成的 ES5 代碼如下:

          var result = (_a = obj.customMethod) === null
            || _a === void 0 ? void 0 : _a.call(obj);

          另外在使用可選調(diào)用的時(shí)候,我們要注意以下兩個(gè)注意事項(xiàng):

          • 如果存在一個(gè)屬性名且該屬性名對(duì)應(yīng)的值不是函數(shù)類型,使用 ?. 仍然會(huì)產(chǎn)生一個(gè) TypeError 異常。
          • 可選鏈的運(yùn)算行為被局限在屬性的訪問、調(diào)用以及元素的訪問 —— 它不會(huì)沿伸到后續(xù)的表達(dá)式中,也就是說可選調(diào)用不會(huì)阻止 a?.b / someMethod() 表達(dá)式中的除法運(yùn)算或 someMethod 的方法調(diào)用。

          ??  繼續(xù)閱讀遇到訪問對(duì)象深層次屬性怎么辦?可選鏈了解一下!

          三、?? 空值合并運(yùn)算符

          在 TypeScript 3.7 版本中除了引入了前面介紹的可選鏈 ?. 之外,也引入了一個(gè)新的邏輯運(yùn)算符 —— 空值合并運(yùn)算符 ??。當(dāng)左側(cè)操作數(shù)為 null 或 undefined 時(shí),其返回右側(cè)的操作數(shù),否則返回左側(cè)的操作數(shù)。

          與邏輯或 || 運(yùn)算符不同,邏輯或會(huì)在左操作數(shù)為 falsy 值時(shí)返回右側(cè)操作數(shù)。也就是說,如果你使用 || 來為某些變量設(shè)置默認(rèn)的值時(shí),你可能會(huì)遇到意料之外的行為。比如為 falsy 值(''、NaN 或 0)時(shí)。

          這里來看一個(gè)具體的例子:

          const foo = null ?? 'default string';
          console.log(foo); // 輸出:"default string"

          const baz = 0 ?? 42;
          console.log(baz); // 輸出:0

          以上 TS 代碼經(jīng)過編譯后,會(huì)生成以下 ES5 代碼:

          "use strict";
          var _a, _b;
          var foo = (_a = null) !== null && _a !== void 0 ? _a : 'default string';
          console.log(foo); // 輸出:"default string"

          var baz = (_b = 0) !== null && _b !== void 0 ? _b : 42;
          console.log(baz); // 輸出:0

          通過觀察以上代碼,我們更加直觀的了解到,空值合并運(yùn)算符是如何解決前面 || 運(yùn)算符存在的潛在問題。下面我們來介紹空值合并運(yùn)算符的特性和使用時(shí)的一些注意事項(xiàng)。

          3.1 短路

          當(dāng)空值合并運(yùn)算符的左表達(dá)式不為 nullundefined 時(shí),不會(huì)對(duì)右表達(dá)式進(jìn)行求值。

          function A(console.log('A was called'); return undefined;}
          function B(console.log('B was called'); return false;}
          function C(console.log('C was called'); return "foo";}

          console.log(A() ?? C());
          console.log(B() ?? C());

          上述代碼運(yùn)行后,控制臺(tái)會(huì)輸出以下結(jié)果:

          A was called 
          C was called 
          foo 
          B was called 
          false 

          3.2 不能與 && 或 || 操作符共用

          若空值合并運(yùn)算符 ?? 直接與 AND(&&)和 OR(||)操作符組合使用 ?? 是不行的。這種情況下會(huì)拋出 SyntaxError。

          // '||' and '??' operations cannot be mixed without parentheses.(5076)
          null || undefined ?? "foo"// raises a SyntaxError

          // '&&' and '??' operations cannot be mixed without parentheses.(5076)
          true && undefined ?? "foo"// raises a SyntaxError

          但當(dāng)使用括號(hào)來顯式表明優(yōu)先級(jí)時(shí)是可行的,比如:

          (null || undefined ) ?? "foo"// 返回 "foo"

          3.3 與可選鏈操作符 ?. 的關(guān)系

          空值合并運(yùn)算符針對(duì) undefined 與 null 這兩個(gè)值,可選鏈?zhǔn)讲僮鞣??.  也是如此。可選鏈?zhǔn)讲僮鞣?,?duì)于訪問屬性可能為 undefined 與 null 的對(duì)象時(shí)非常有用。

          interface Customer {
            name: string;
            city?: string;
          }

          let customer: Customer = {
            name: "Semlinker"
          };

          let customerCity = customer?.city ?? "Unknown city";
          console.log(customerCity); // 輸出:Unknown city

          前面我們已經(jīng)介紹了空值合并運(yùn)算符的應(yīng)用場(chǎng)景和使用時(shí)的一些注意事項(xiàng),該運(yùn)算符不僅可以在 TypeScript 3.7 以上版本中使用。當(dāng)然你也可以在 JavaScript 的環(huán)境中使用它,但你需要借助 Babel,在 Babel 7.8.0 版本也開始支持空值合并運(yùn)算符。

          ??  繼續(xù)閱讀在 TS 中你踩過默認(rèn)值的坑么?給 ?? 運(yùn)算符把坑填了!

          四、?: 可選屬性

          在面向?qū)ο笳Z言中,接口是一個(gè)很重要的概念,它是對(duì)行為的抽象,而具體如何行動(dòng)需要由類去實(shí)現(xiàn)。TypeScript 中的接口是一個(gè)非常靈活的概念,除了可用于對(duì)類的一部分行為進(jìn)行抽象以外,也常用于對(duì)「對(duì)象的形狀(Shape)」進(jìn)行描述。

          在 TypeScript 中使用 interface 關(guān)鍵字就可以聲明一個(gè)接口:

          interface Person {
            name: string;
            age: number;
          }

          let semlinker: Person = {
            name: "semlinker",
            age: 33,
          };

          在以上代碼中,我們聲明了 Person 接口,它包含了兩個(gè)必填的屬性 nameage。在初始化 Person 類型變量時(shí),如果缺少某個(gè)屬性,TypeScript 編譯器就會(huì)提示相應(yīng)的錯(cuò)誤信息,比如:

          // Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.(2741)
          let lolo: Person  = { // Error
            name: "lolo"  
          }

          為了解決上述的問題,我們可以把某個(gè)屬性聲明為可選的:

          interface Person {
            name: string;
            age?: number;
          }

          let lolo: Person  = {
            name: "lolo"  
          }

          4.1 工具類型

          4.1.1 Partial<T>

          在實(shí)際項(xiàng)目開發(fā)過程中,為了提高代碼復(fù)用率,我們可以利用 TypeScript 內(nèi)置的工具類型 Partial<T> 來快速把某個(gè)接口類型中定義的屬性變成可選的:

          interface PullDownRefreshConfig {
            threshold: number;
            stop: number;
          }

          /**
           * type PullDownRefreshOptions = {
           *   threshold?: number | undefined;
           *   stop?: number | undefined;
           * }
           */
           
          type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

          是不是覺得 Partial<T> 很方便,下面讓我們來看一下它是如何實(shí)現(xiàn)的:

          /**
           * Make all properties in T optional
           */

          type Partial<T> = {
            [P in keyof T]?: T[P];
          };
          4.1.2 Required<T>

          既然可以快速地把某個(gè)接口中定義的屬性全部聲明為可選,那能不能把所有的可選的屬性變成必選的呢?答案是可以的,針對(duì)這個(gè)需求,我們可以使用 Required<T> 工具類型,具體的使用方式如下:

          interface PullDownRefreshConfig {
            threshold: number;
            stop: number;
          }

          type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

          /**
           * type PullDownRefresh = {
           *   threshold: number;
           *   stop: number;
           * }
           */

          type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>

          同樣,我們來看一下 Required<T> 工具類型是如何實(shí)現(xiàn)的:

          /**
           * Make all properties in T required
           */

          type Required<T> = {
            [P in keyof T]-?: T[P];
          };

          原來在 Required<T> 工具類型內(nèi)部,通過 -? 移除了可選屬性中的 ?,使得屬性從可選變?yōu)楸剡x的。

          ??  繼續(xù)閱讀掌握 TS 這些工具類型,讓你開發(fā)事半功倍

          五、& 運(yùn)算符

          在 TypeScript 中交叉類型是將多個(gè)類型合并為一個(gè)類型。通過 & 運(yùn)算符可以將現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。

          type PartialPointX = { x: number; };
          type Point = PartialPointX & { y: number; };

          let point: Point = {
            x: 1,
            y: 1
          }

          在上面代碼中我們先定義了 PartialPointX 類型,接著使用 & 運(yùn)算符創(chuàng)建一個(gè)新的 Point 類型,表示一個(gè)含有 x 和 y 坐標(biāo)的點(diǎn),然后定義了一個(gè) Point 類型的變量并初始化。

          5.1 同名基礎(chǔ)類型屬性的合并

          那么現(xiàn)在問題來了,假設(shè)在合并多個(gè)類型的過程中,剛好出現(xiàn)某些類型存在相同的成員,但對(duì)應(yīng)的類型又不一致,比如:

          interface X {
            c: string;
            d: string;
          }

          interface Y {
            c: number;
            e: string
          }

          type XY = X & Y;
          type YX = Y & X;

          let p: XY;
          let q: YX;

          在上面的代碼中,接口 X  和接口 Y 都含有一個(gè)相同的成員 c,但它們的類型不一致。對(duì)于這種情況,此時(shí) XY 類型或 YX 類型中成員 c 的類型是不是可以是 stringnumber 類型呢?比如下面的例子:

          p = { c: 6, d: "d", e: "e" }; 

          q = { c: "c", d: "d", e: "e" }; 

          為什么接口 X 和接口 Y 混入后,成員 c 的類型會(huì)變成 never 呢?這是因?yàn)榛烊牒蟪蓡T c 的類型為 string & number,即成員 c 的類型既可以是 string 類型又可以是 number 類型。很明顯這種類型是不存在的,所以混入后成員 c 的類型為 never

          5.2 同名非基礎(chǔ)類型屬性的合并

          在上面示例中,剛好接口 X 和接口 Y 中內(nèi)部成員 c 的類型都是基本數(shù)據(jù)類型,那么如果是非基本數(shù)據(jù)類型的話,又會(huì)是什么情形。我們來看個(gè)具體的例子:

          interface D { d: boolean; }
          interface E { e: string; }
          interface F { f: number; }

          interface A { x: D; }
          interface B { x: E; }
          interface C { x: F; }

          type ABC = A & B & C;

          let abc: ABC = {
            x: {
              d: true,
              e: 'semlinker',
              f: 666
            }
          };

          console.log('abc:', abc);

          以上代碼成功運(yùn)行后,控制臺(tái)會(huì)輸出以下結(jié)果:


          由上圖可知,在混入多個(gè)類型時(shí),若存在相同的成員,且成員類型為非基本數(shù)據(jù)類型,那么是可以成功合并。

          ??  繼續(xù)閱讀TypeScript 交叉類型

          六、| 分隔符

          在 TypeScript 中聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種,聯(lián)合類型使用 | 分隔每個(gè)類型。聯(lián)合類型通常與 nullundefined 一起使用:

          const sayHello = (name: string | undefined) => { /* ... */ };

          以上示例中 name 的類型是 string | undefined 意味著可以將 stringundefined 的值傳遞給 sayHello 函數(shù)。

          sayHello("semlinker");
          sayHello(undefined);

          此外,對(duì)于聯(lián)合類型來說,你可能會(huì)遇到以下的用法:

          let num: 1 | 2 = 1;
          type EventNames = 'click' | 'scroll' | 'mousemove';

          示例中的 12'click' 被稱為字面量類型,用來約束取值只能是某幾個(gè)值中的一個(gè)。

          6.1 類型保護(hù)

          當(dāng)使用聯(lián)合類型時(shí),我們必須盡量把當(dāng)前值的類型收窄為當(dāng)前值的實(shí)際類型,而類型保護(hù)就是實(shí)現(xiàn)類型收窄的一種手段。

          類型保護(hù)是可執(zhí)行運(yùn)行時(shí)檢查的一種表達(dá)式,用于確保該類型在一定的范圍內(nèi)。換句話說,類型保護(hù)可以保證一個(gè)字符串是一個(gè)字符串,盡管它的值也可以是一個(gè)數(shù)字。類型保護(hù)與特性檢測(cè)并不是完全不同,其主要思想是嘗試檢測(cè)屬性、方法或原型,以確定如何處理值。

          目前主要有四種的方式來實(shí)現(xiàn)類型保護(hù):

          6.1.1 in 關(guān)鍵字
          interface Admin {
            name: string;
            privileges: string[];
          }

          interface Employee {
            name: string;
            startDate: Date;
          }

          type UnknownEmployee = Employee | Admin;

          function printEmployeeInformation(emp: UnknownEmployee{
            console.log("Name: " + emp.name);
            if ("privileges" in emp) {
              console.log("Privileges: " + emp.privileges);
            }
            if ("startDate" in emp) {
              console.log("Start Date: " + emp.startDate);
            }
          }
          6.1.2 typeof 關(guān)鍵字
          function padLeft(value: string, padding: string | number{
            if (typeof padding === "number") {
                return Array(padding + 1).join(" ") + value;
            }
            if (typeof padding === "string") {
                return padding + value;
            }
            throw new Error(`Expected string or number, got '${padding}'.`);
          }

          typeof 類型保護(hù)只支持兩種形式:typeof v === "typename"typeof v !== typename,"typename" 必須是 "number""string", "boolean""symbol"。但是 TypeScript 并不會(huì)阻止你與其它字符串比較,語言不會(huì)把那些表達(dá)式識(shí)別為類型保護(hù)。

          6.1.3 instanceof 關(guān)鍵字
          interface Padder {
            getPaddingString(): string;
          }

          class SpaceRepeatingPadder implements Padder {
            constructor(private numSpaces: number) {}
            getPaddingString() {
              return Array(this.numSpaces + 1).join(" ");
            }
          }

          class StringPadder implements Padder {
            constructor(private value: string) {}
            getPaddingString() {
              return this.value;
            }
          }

          let padder: Padder = new SpaceRepeatingPadder(6);

          if (padder instanceof SpaceRepeatingPadder) {
            // padder的類型收窄為 'SpaceRepeatingPadder'
          }
          6.1.4 自定義類型保護(hù)的類型謂詞(type predicate)
          function isNumber(x: any): x is number {
            return typeof x === "number";
          }

          function isString(x: any): x is string {
            return typeof x === "string";
          }

          ??  繼續(xù)閱讀讀懂 TS 中聯(lián)合類型和交叉類型的含義

          七、_ 數(shù)字分隔符

          TypeScript 2.7 帶來了對(duì)數(shù)字分隔符的支持,正如數(shù)值分隔符 ECMAScript 提案中所概述的那樣。對(duì)于一個(gè)數(shù)字字面量,你現(xiàn)在可以通過把一個(gè)下劃線作為它們之間的分隔符來分組數(shù)字:

          const inhabitantsOfMunich = 1_464_301;
          const distanceEarthSunInKm = 149_600_000;
          const fileSystemPermission = 0b111_111_000;
          const bytes = 0b1111_10101011_11110000_00001101;

          分隔符不會(huì)改變數(shù)值字面量的值,但邏輯分組使人們更容易一眼就能讀懂?dāng)?shù)字。以上 TS 代碼經(jīng)過編譯后,會(huì)生成以下 ES5 代碼:

          "use strict";
          var inhabitantsOfMunich = 1464301;
          var distanceEarthSunInKm = 149600000;
          var fileSystemPermission = 504;
          var bytes = 262926349;

          7.1 使用限制

          雖然數(shù)字分隔符看起來很簡(jiǎn)單,但在使用時(shí)還是有一些限制。比如你只能在兩個(gè)數(shù)字之間添加 _ 分隔符。以下的使用方式是非法的:

          // Numeric separators are not allowed here.(6188)
          3_.141592 // Error
          3._141592 // Error

          // Numeric separators are not allowed here.(6188)
          1_e10 // Error
          1e_10 // Error

          // Cannot find name '_126301'.(2304)
          _126301  // Error
          // Numeric separators are not allowed here.(6188)
          126301// Error

          // Cannot find name 'b111111000'.(2304)
          // An identifier or keyword cannot immediately follow a numeric literal.(1351)
          0_b111111000 // Error

          // Numeric separators are not allowed here.(6188)
          0b_111111000 // Error

          當(dāng)然你也不能連續(xù)使用多個(gè) _ 分隔符,比如:

          // Multiple consecutive numeric separators are not permitted.(6189)
          123__456 // Error

          7.2 解析分隔符

          此外,需要注意的是以下用于解析數(shù)字的函數(shù)是不支持分隔符:

          • Number()
          • parseInt()
          • parseFloat()

          這里我們來看一下實(shí)際的例子:

          Number('123_456')
          NaN
          parseInt('123_456')
          123
          parseFloat('123_456')
          123

          很明顯對(duì)于以上的結(jié)果不是我們所期望的,所以在處理分隔符時(shí)要特別注意。當(dāng)然要解決上述問題,也很簡(jiǎn)單只需要非數(shù)字的字符刪掉即可。這里我們來定義一個(gè) removeNonDigits 的函數(shù):

          const RE_NON_DIGIT = /[^0-9]/gu;

          function removeNonDigits(str{
            str = str.replace(RE_NON_DIGIT, '');
            return Number(str);
          }

          該函數(shù)通過調(diào)用字符串的 replace 方法來移除非數(shù)字的字符,具體的使用方式如下:

          removeNonDigits('123_456')
          123456
          removeNonDigits('149,600,000')
          149600000
          removeNonDigits('1,407,836')
          1407836

          八、<Type> 語法

          8.1 TypeScript 斷言

          有時(shí)候你會(huì)遇到這樣的情況,你會(huì)比 TypeScript 更了解某個(gè)值的詳細(xì)信息。通常這會(huì)發(fā)生在你清楚地知道一個(gè)實(shí)體具有比它現(xiàn)有類型更確切的類型。

          通過類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在干什么”。類型斷言好比其他語言里的類型轉(zhuǎn)換,但是不進(jìn)行特殊的數(shù)據(jù)檢查和解構(gòu)。它沒有運(yùn)行時(shí)的影響,只是在編譯階段起作用。

          類型斷言有兩種形式:

          8.1.1 “尖括號(hào)” 語法
          let someValue: any = "this is a string";
          let strLength: number = (<string>someValue).length;
          8.1.2 as 語法
          let someValue: any = "this is a string";
          let strLength: number = (someValue as string).length;

          8.2 TypeScript 泛型

          對(duì)于剛接觸 TypeScript 泛型的讀者來說,首次看到 <T> 語法會(huì)感到陌生。其實(shí)它沒有什么特別,就像傳遞參數(shù)一樣,我們傳遞了我們想要用于特定函數(shù)調(diào)用的類型。

          參考上面的圖片,當(dāng)我們調(diào)用 identity<Number>(1) ,Number 類型就像參數(shù) 1 一樣,它將在出現(xiàn) T 的任何位置填充該類型。圖中 <T> 內(nèi)部的 T 被稱為類型變量,它是我們希望傳遞給 identity 函數(shù)的類型占位符,同時(shí)它被分配給 value 參數(shù)用來代替它的類型:此時(shí) T 充當(dāng)?shù)氖穷愋?,而不是特定?Number 類型。

          其中 T 代表 Type,在定義泛型時(shí)通常用作第一個(gè)類型變量名稱。但實(shí)際上 T 可以用任何有效名稱代替。除了 T 之外,以下是常見泛型變量代表的意思:

          • K(Key):表示對(duì)象中的鍵類型;
          • V(Value):表示對(duì)象中的值類型;
          • E(Element):表示元素類型。

          其實(shí)并不是只能定義一個(gè)類型變量,我們可以引入希望定義的任何數(shù)量的類型變量。比如我們引入一個(gè)新的類型變量 U,用于擴(kuò)展我們定義的 identity 函數(shù):

          function identity <TU>(value: T, message: U) : T {
            console.log(message);
            return value;
          }

          console.log(identity<Numberstring>(68"Semlinker"));

          除了為類型變量顯式設(shè)定值之外,一種更常見的做法是使編譯器自動(dòng)選擇這些類型,從而使代碼更簡(jiǎn)潔。我們可以完全省略尖括號(hào),比如:

          function identity <TU>(value: T, message: U) : T {
            console.log(message);
            return value;
          }

          console.log(identity(68"Semlinker"));

          對(duì)于上述代碼,編譯器足夠聰明,能夠知道我們的參數(shù)類型,并將它們賦值給 T 和 U,而不需要開發(fā)人員顯式指定它們。

          ??  繼續(xù)閱讀一文讀懂 TypeScript 泛型及應(yīng)用

          九、@XXX 裝飾器

          9.1 裝飾器語法

          對(duì)于一些剛接觸 TypeScript 的小伙伴來說,在第一次看到 @Plugin({...}) 這種語法可能會(huì)覺得很驚訝。其實(shí)這是裝飾器的語法,裝飾器的本質(zhì)是一個(gè)函數(shù),通過裝飾器我們可以方便地定義與對(duì)象相關(guān)的元數(shù)據(jù)。

          @Plugin({
            pluginName: 'Device',
            plugin: 'cordova-plugin-device',
            pluginRef: 'device',
            repo: 'https://github.com/apache/cordova-plugin-device',
            platforms: ['Android''Browser''iOS''macOS''Windows'],
          })
          @Injectable()
          export class Device extends IonicNativePlugin {}

          在以上代碼中,我們通過裝飾器來保存 ionic-native 插件的相關(guān)元信息,而 @Plugin({...}) 中的 @ 符號(hào)只是語法糖,為什么說是語法糖呢?這里我們來看一下編譯生成的 ES5 代碼:

          var __decorate = (this && this.__decorate) || function (decorators, target, key, desc{
              var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
              if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
              else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
              return c > 3 && r && Object.defineProperty(target, key, r), r;
          };

          var Device = /** @class */ (function (_super{
              __extends(Device, _super);
              function Device({
                  return _super !== null && _super.apply(thisarguments) || this;
              }
              Device = __decorate([
                  Plugin({
                      pluginName'Device',
                      plugin'cordova-plugin-device',
                      pluginRef'device',
                      repo'https://github.com/apache/cordova-plugin-device',
                      platforms: ['Android''Browser''iOS''macOS''Windows'],
                  }),
                  Injectable()
              ], Device);
              return Device;
          }(IonicNativePlugin));

          通過生成的代碼可知,@Plugin({...})@Injectable() 最終會(huì)被轉(zhuǎn)換成普通的方法調(diào)用,它們的調(diào)用結(jié)果最終會(huì)以數(shù)組的形式作為參數(shù)傳遞給 __decorate 函數(shù),而在 __decorate 函數(shù)內(nèi)部會(huì)以 Device 類作為參數(shù)調(diào)用各自的類型裝飾器,從而擴(kuò)展對(duì)應(yīng)的功能。

          9.2 裝飾器的分類

          在 TypeScript 中裝飾器分為類裝飾器、屬性裝飾器、方法裝飾器和參數(shù)裝飾器四大類。

          9.2.1 類裝飾器

          類裝飾器聲明:

          declare type ClassDecorator = <TFunction extends Function>(
            target: TFunction
          ) => TFunction | void;

          類裝飾器顧名思義,就是用來裝飾類的。它接收一個(gè)參數(shù):

          • target: TFunction - 被裝飾的類

          看完第一眼后,是不是感覺都不好了。沒事,我們馬上來個(gè)例子:

          function Greeter(target: Function): void {
            target.prototype.greet = function (): void {
              console.log("Hello Semlinker!");
            };
          }

          @Greeter
          class Greeting {
            constructor() {
              // 內(nèi)部實(shí)現(xiàn)
            }
          }

          let myGreeting = new Greeting();
          myGreeting.greet(); // console output: 'Hello Semlinker!';

          上面的例子中,我們定義了 Greeter 類裝飾器,同時(shí)我們使用了 @Greeter 語法糖,來使用裝飾器。

          友情提示:讀者可以直接復(fù)制上面的代碼,在 TypeScript Playground 中運(yùn)行查看結(jié)果。

          9.2.2 屬性裝飾器

          屬性裝飾器聲明:

          declare type PropertyDecorator = (target:Object
            propertyKey: string | symbol ) => void;

          屬性裝飾器顧名思義,用來裝飾類的屬性。它接收兩個(gè)參數(shù):

          • target: Object - 被裝飾的類
          • propertyKey: string | symbol - 被裝飾類的屬性名

          趁熱打鐵,馬上來個(gè)例子熱熱身:

          function logProperty(target: any, key: string{
            delete target[key];

            const backingField = "_" + key;

            Object.defineProperty(target, backingField, {
              writable: true,
              enumerable: true,
              configurable: true
            });

            // property getter
            const getter = function (thisany{
              const currVal = this[backingField];
              console.log(`Get: ${key} => ${currVal}`);
              return currVal;
            };

            // property setter
            const setter = function (thisany, newVal: any{
              console.log(`Set: ${key} => ${newVal}`);
              this[backingField] = newVal;
            };

            // Create new property with getter and setter
            Object.defineProperty(target, key, {
              get: getter,
              set: setter,
              enumerable: true,
              configurable: true
            });
          }

          class Person { 
            @logProperty
            public name: string;

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

          const p1 = new Person("semlinker");
          p1.name = "kakuqo";

          以上代碼我們定義了一個(gè) logProperty 函數(shù),來跟蹤用戶對(duì)屬性的操作,當(dāng)代碼成功運(yùn)行后,在控制臺(tái)會(huì)輸出以下結(jié)果:

          Set: name => semlinker
          Set: name => kakuqo
          9.2.3 方法裝飾器

          方法裝飾器聲明:

          declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,    
            descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

          方法裝飾器顧名思義,用來裝飾類的方法。它接收三個(gè)參數(shù):

          • target: Object - 被裝飾的類
          • propertyKey: string | symbol - 方法名
          • descriptor: TypePropertyDescript - 屬性描述符

          廢話不多說,直接上例子:

          function LogOutput(tarage: Function, key: string, descriptor: any{
            let originalMethod = descriptor.value;
            let newMethod = function(...args: any[]): any {
              let result: any = originalMethod.apply(this, args);
              if(!this.loggedOutput) {
                this.loggedOutput = new Array<any>();
              }
              this.loggedOutput.push({
                method: key,
                parameters: args,
                output: result,
                timestamp: new Date()
              });
              return result;
            };
            descriptor.value = newMethod;
          }

          class Calculator {
            @LogOutput
            double (num: number): number {
              return num * 2;
            }
          }

          let calc = new Calculator();
          calc.double(11);
          // console ouput: [{method: "double", output: 22, ...}]
          console.log(calc.loggedOutput); 
          9.2.4 參數(shù)裝飾器

          參數(shù)裝飾器聲明:

          declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, 
            parameterIndex: number ) => void

          參數(shù)裝飾器顧名思義,是用來裝飾函數(shù)參數(shù),它接收三個(gè)參數(shù):

          • target: Object - 被裝飾的類
          • propertyKey: string | symbol - 方法名
          • parameterIndex: number - 方法中參數(shù)的索引值
          function Log(target: Function, key: string, parameterIndex: number{
            let functionLogged = key || target.prototype.constructor.name;
            console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
           been decorated`
          );
          }

          class Greeter {
            greeting: string;
            constructor(@Log phrase: string) {
           this.greeting = phrase; 
            }
          }

          // console output: The parameter in position 0 
          // at Greeter has been decorated

          ??  繼續(xù)閱讀:覺得裝飾器有點(diǎn)難?那就進(jìn)來看看唄

          十、#XXX 私有字段

          在 TypeScript 3.8 版本就開始支持 ECMAScript 私有字段,使用方式如下:

          class Person {
            #name: string;

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

            greet() {
              console.log(`Hello, my name is ${this.#name}!`);
            }
          }

          let semlinker = new Person("Semlinker");

          semlinker.#name;
          //     ~~~~~
          // Property '#name' is not accessible outside class 'Person'
          // because it has a private identifier.

          與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同,私有字段要牢記以下規(guī)則:

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

          10.1 私有字段與 private 的區(qū)別

          說到這里使用 # 定義的私有字段與 private 修飾符定義字段有什么區(qū)別呢?現(xiàn)在我們先來看一個(gè) private 的示例:

          class Person {
            constructor(private name: string){}
          }

          let person = new Person("Semlinker");
          console.log(person.name);

          在上面代碼中,我們創(chuàng)建了一個(gè) Person 類,該類中使用 private 修飾符定義了一個(gè)私有屬性 name,接著使用該類創(chuàng)建一個(gè) person 對(duì)象,然后通過 person.name 來訪問 person 對(duì)象的私有屬性,這時(shí) TypeScript 編譯器會(huì)提示以下異常:

          Property 'name' is private and only accessible within class 'Person'.(2341)

          那如何解決這個(gè)異常呢?當(dāng)然你可以使用類型斷言把 person 轉(zhuǎn)為 any 類型:

          console.log((person as any).name);

          通過這種方式雖然解決了 TypeScript 編譯器的異常提示,但是在運(yùn)行時(shí)我們還是可以訪問到 Person 類內(nèi)部的私有屬性,為什么會(huì)這樣呢?我們來看一下編譯生成的 ES5 代碼,也許你就知道答案了:

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

          var person = new Person("Semlinker");
          console.log(person.name);

          這時(shí)相信有些小伙伴會(huì)好奇,在 TypeScript 3.8 以上版本通過 # 號(hào)定義的私有字段編譯后會(huì)生成什么代碼:

          class Person {
            #name: string;

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

            greet() {
              console.log(`Hello, my name is ${this.#name}!`);
            }
          }

          以上代碼目標(biāo)設(shè)置為 ES2015,會(huì)編譯生成以下代碼:

          "use strict";
          var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) 
            || function (receiver, privateMap, value{
              if (!privateMap.has(receiver)) {
                throw new TypeError("attempted to set private field on non-instance");
              }
              privateMap.set(receiver, value);
              return value;
          };

          var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) 
            || function (receiver, privateMap{
              if (!privateMap.has(receiver)) {
                throw new TypeError("attempted to get private field on non-instance");
              }
              return privateMap.get(receiver);
          };

          var _name;
          class Person {
              constructor(name) {
                _name.set(thisvoid 0);
                __classPrivateFieldSet(this, _name, name);
              }
              greet() {
                console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);
              }
          }
          _name = new WeakMap();

          通過觀察上述代碼,使用 # 號(hào)定義的 ECMAScript 私有字段,會(huì)通過 WeakMap 對(duì)象來存儲(chǔ),同時(shí)編譯器會(huì)生成 __classPrivateFieldSet__classPrivateFieldGet 這兩個(gè)方法用于設(shè)置值和獲取值。

          ??  繼續(xù)閱讀:你不知道的 WeakMap

          以上提到的這些 “奇怪” 的符號(hào),相信一些小伙伴們?cè)趯W(xué)習(xí) TS 過程中也遇到了。如果有表述不清楚的地方,歡迎你們給我留言或直接與我交流。之后,阿寶哥還會(huì)繼續(xù)補(bǔ)充和完善這一方面的內(nèi)容,感興趣的小伙伴可以一起參與喲。

          十一、參考資源

          • ES proposal: numeric separators
          • typescriptlang.org
          推薦閱讀


          細(xì)數(shù)這些年被困擾過的 TS 問題

          細(xì)數(shù)這些年被困擾過的 TS 問題

          了不起的 TypeScript 入門教程

          了不起的 TypeScript 入門教程

          一文讀懂 TypeScript 泛型及應(yīng)用

          一文讀懂 TypeScript 泛型及應(yīng)用

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  婷婷丁香网站 | 黄色片在线免费观看视频 | 久久青榴视频 | 黄色电影网站免费 | 久久人妻狠狠操 |