<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          細數 TS 中那些奇怪的符號

          2020-9-4    seo達人

          TypeScript 是一種由微軟開發的自由和開源的編程語言。它是 JavaScript 的一個超集,而且本質上向這個語言添加了可選的靜態類型和基于類的面向對象編程。


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


          好的,下面我們來開始介紹第一個符號 —— ! 非空斷言操作符。


          一、! 非空斷言操作符

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


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


          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 調用函數時忽略 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 }

          因為 ! 非空斷言操作符會從編譯生成的 JavaScript 代碼中移除,所以在實際使用的過程中,要特別注意。比如下面這個例子:


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

          以上 TS 代碼會編譯生成以下 ES5 代碼:


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

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


          二、?. 運算符

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


          obj?.prop

          obj?.[expr]

          arr?.[index] func?.(args)

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


          const val = a?.b;

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


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

          上述的代碼會自動檢查對象 a 是否為 null 或 undefined,如果是的話就立即返回 undefined,這樣就可以立即停止某些表達式的運行。你可能已經想到可以使用 ?. 來替代很多使用 && 執行空檢查的代碼:


          if(a && a.b) { } if(a?.b){ } /**

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

          *

          * if(

          *  a === null || a === void 0

          *  ? void 0 : a.b) {

          * }

          */

          但需要注意的是,?. 與 && 運算符行為略有不同,&& 專門用于檢測 falsy 值,比如空字符串、0、NaN、null 和 false 等。而 ?. 只會驗證對象是否為 null 或 undefined,對于 0 或空字符串來說,并不會出現 “短路”。


          2.1 可選元素訪問

          可選鏈除了支持可選屬性的訪問之外,它還支持可選元素的訪問,它的行為類似于可選屬性的訪問,只是可選元素的訪問允許我們訪問非標識符的屬性,比如任意字符串、數字索引和 Symbol:


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

          }

          以上代碼經過編譯后會生成以下 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 方法中會自動檢測輸入參數 arr 的值是否為 null 或 undefined,從而保證了我們代碼的健壯性。


          2.2 可選鏈與函數調用

          當嘗試調用一個可能不存在的方法時也可以使用可選鏈。在實際開發過程中,這是很有用的。系統中某個方法不可用,有可能是由于版本不一致或者用戶設備兼容性問題導致的。函數調用時如果被調用的方法不存在,使用可選鏈可以使表達式自動返回 undefined 而不是拋出一個異常。


          可選調用使用起來也很簡單,比如:


          let result = obj.customMethod?.();

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


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

          另外在使用可選調用的時候,我們要注意以下兩個注意事項:


          如果存在一個屬性名且該屬性名對應的值不是函數類型,使用 ?. 仍然會產生一個 TypeError 異常。

          可選鏈的運算行為被局限在屬性的訪問、調用以及元素的訪問 —— 它不會沿伸到后續的表達式中,也就是說可選調用不會阻止 a?.b / someMethod() 表達式中的除法運算或 someMethod 的方法調用。

          三、?? 空值合并運算符

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


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


          這里來看一個具體的例子:


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

          以上 TS 代碼經過編譯后,會生成以下 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

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


          3.1 短路

          當空值合并運算符的左表達式不為 null 或 undefined 時,不會對右表達式進行求值。


          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());

          上述代碼運行后,控制臺會輸出以下結果:


          A was called

          C was called

          foo

          B was called

          false

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

          若空值合并運算符 ?? 直接與 AND(&&)和 OR(||)操作符組合使用 ?? 是不行的。這種情況下會拋出 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

          但當使用括號來顯式表明優先級時是可行的,比如:


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

          3.3 與可選鏈操作符 ?. 的關系

          空值合并運算符針對 undefined 與 null 這兩個值,可選鏈式操作符 ?. 也是如此??蛇x鏈式操作符,對于訪問屬性可能為 undefined 與 null 的對象時非常有用。


          interface Customer {

           name: string;

           city?: string;

          } let customer: Customer = {

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

          前面我們已經介紹了空值合并運算符的應用場景和使用時的一些注意事項,該運算符不僅可以在 TypeScript 3.7 以上版本中使用。當然你也可以在 JavaScript 的環境中使用它,但你需要借助 Babel,在 Babel 7.8.0 版本也開始支持空值合并運算符。


          四、?: 可選屬性

          在面向對象語言中,接口是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類去實現。 TypeScript 中的接口是一個非常靈活的概念,除了可用于對類的一部分行為進行抽象以外,也常用于對「對象的形狀(Shape)」進行描述。


          在 TypeScript 中使用 interface 關鍵字就可以聲明一個接口:


          interface Person {

           name: string;

           age: number;

          } let semlinker: Person = {

           name: "semlinker",

           age: 33,

          };

          在以上代碼中,我們聲明了 Person 接口,它包含了兩個必填的屬性 name 和 age。在初始化 Person 類型變量時,如果缺少某個屬性,TypeScript 編譯器就會提示相應的錯誤信息,比如:


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

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


          interface Person {

           name: string;

           age?: number;

          } let lolo: Person  = {

           name: "lolo" }

          4.1 工具類型

          4.1.1 Partial<T>

          在實際項目開發過程中,為了提高代碼復用率,我們可以利用 TypeScript 內置的工具類型 Partial<T> 來快速把某個接口類型中定義的屬性變成可選的:


          interface PullDownRefreshConfig {

           threshold: number;

           stop: number;

          } /**

          * type PullDownRefreshOptions = {

          *   threshold?: number | undefined;

          *   stop?: number | undefined;

          * }

          */ type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

          是不是覺得 Partial<T> 很方便,下面讓我們來看一下它是如何實現的:


          /**

          * Make all properties in T optional

          */ type Partial<T> = {

           [P in keyof T]?: T[P];

          };

          4.1.2 Required<T>

          既然可以快速地把某個接口中定義的屬性全部聲明為可選,那能不能把所有的可選的屬性變成必選的呢?答案是可以的,針對這個需求,我們可以使用 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> 工具類型是如何實現的:


          /**

          * Make all properties in T required

          */ type Required<T> = {

           [P in keyof T]-?: T[P];

          };

          原來在 Required<T> 工具類型內部,通過 -? 移除了可選屬性中的 ?,使得屬性從可選變為必選的。


          五、& 運算符

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


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

           x: 1,

           y: 1 }

          在上面代碼中我們先定義了 PartialPointX 類型,接著使用 & 運算符創建一個新的 Point 類型,表示一個含有 x 和 y 坐標的點,然后定義了一個 Point 類型的變量并初始化。


          5.1 同名基礎類型屬性的合并

          那么現在問題來了,假設在合并多個類型的過程中,剛好出現某些類型存在相同的成員,但對應的類型又不一致,比如:


          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 都含有一個相同的成員 c,但它們的類型不一致。對于這種情況,此時 XY 類型或 YX 類型中成員 c 的類型是不是可以是 string 或 number 類型呢?比如下面的例子:


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



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



          為什么接口 X 和接口 Y 混入后,成員 c 的類型會變成 never 呢?這是因為混入后成員 c 的類型為 string & number,即成員 c 的類型既可以是 string 類型又可以是 number 類型。很明顯這種類型是不存在的,所以混入后成員 c 的類型為 never。


          5.2 同名非基礎類型屬性的合并

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


          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);

          以上代碼成功運行后,控制臺會輸出以下結果:




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


          六、| 分隔符

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


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

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


          sayHello("semlinker");

          sayHello(undefined);

          此外,對于聯合類型來說,你可能會遇到以下的用法:


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

          示例中的 1、2 或 'click' 被稱為字面量類型,用來約束取值只能是某幾個值中的一個。


          6.1 類型保護

          當使用聯合類型時,我們必須盡量把當前值的類型收窄為當前值的實際類型,而類型保護就是實現類型收窄的一種手段。


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


          目前主要有四種的方式來實現類型保護:


          6.1.1 in 關鍵字

          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 關鍵字

          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 類型保護只支持兩種形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必須是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護。


          6.1.3 instanceof 關鍵字

          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 自定義類型保護的類型謂詞(type predicate)

          function isNumber(x: any): x is number { return typeof x === "number";

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

          }

          七、_ 數字分隔符

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


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

          分隔符不會改變數值字面量的值,但邏輯分組使人們更容易一眼就能讀懂數字。以上 TS 代碼經過編譯后,會生成以下 ES5 代碼:


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

          7.1 使用限制

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


          // 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

          當然你也不能連續使用多個 _ 分隔符,比如:


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

          7.2 解析分隔符

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


          Number()

          parseInt()

          parseFloat()

          這里我們來看一下實際的例子:


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

          很明顯對于以上的結果不是我們所期望的,所以在處理分隔符時要特別注意。當然要解決上述問題,也很簡單只需要非數字的字符刪掉即可。這里我們來定義一個 removeNonDigits 的函數:


          const RE_NON_DIGIT = /[^0-9]/gu; function removeNonDigits(str) {

           str = str.replace(RE_NON_DIGIT, ''); return Number(str);

          }

          該函數通過調用字符串的 replace 方法來移除非數字的字符,具體的使用方式如下:


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

          八、<Type> 語法

          8.1 TypeScript 斷言

          有時候你會遇到這樣的情況,你會比 TypeScript 更了解某個值的詳細信息。通常這會發生在你清楚地知道一個實體具有比它現有類型更確切的類型。


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


          類型斷言有兩種形式:


          8.1.1 “尖括號” 語法

          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 泛型

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




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


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


          K(Key):表示對象中的鍵類型;

          V(Value):表示對象中的值類型;

          E(Element):表示元素類型。

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


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

          } console.log(identity<Number, string>(68, "Semlinker"));



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


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

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

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


          九、@XXX 裝飾器

          9.1 裝飾器語法

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


          @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 插件的相關元信息,而 @Plugin({...}) 中的 @ 符號只是語法糖,為什么說是語法糖呢?這里我們來看一下編譯生成的 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(this, arguments) || 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() 最終會被轉換成普通的方法調用,它們的調用結果最終會以數組的形式作為參數傳遞給 __decorate 函數,而在 __decorate 函數內部會以 Device 類作為參數調用各自的類型裝飾器,從而擴展對應的功能。


          9.2 裝飾器的分類

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


          9.2.1 類裝飾器

          類裝飾器聲明:


          declare type ClassDecorator = <TFunction extends Function>(

           target: TFunction

          ) => TFunction | void;

          類裝飾器顧名思義,就是用來裝飾類的。它接收一個參數:


          target: TFunction - 被裝飾的類

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


          function Greeter(target: Function): void {

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

           };

          } @Greeter class Greeting { constructor() { // 內部實現 }

          } let myGreeting = new Greeting();

          myGreeting.greet(); // console output: 'Hello Semlinker!';

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


          友情提示:讀者可以直接復制上面的代碼,在 TypeScript Playground 中運行查看結果。

          9.2.2 屬性裝飾器

          屬性裝飾器聲明:


          declare type PropertyDecorator = (target:Object,

           propertyKey: string | symbol ) => void;

          屬性裝飾器顧名思義,用來裝飾類的屬性。它接收兩個參數:


          target: Object - 被裝飾的類

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

          趁熱打鐵,馬上來個例子熱熱身:


          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 (this: any) { const currVal = this[backingField]; console.log(`Get: ${key} => ${currVal}`); return currVal;

           }; // property setter const setter = function (this: any, 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";

          以上代碼我們定義了一個 logProperty 函數,來跟蹤用戶對屬性的操作,當代碼成功運行后,在控制臺會輸出以下結果:


          Set: name => semlinker Set: name => kakuqo

          9.2.3 方法裝飾器

          方法裝飾器聲明:


          declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,          

           descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

          方法裝飾器顧名思義,用來裝飾類的方法。它接收三個參數:


          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 參數裝飾器

          參數裝飾器聲明:


          declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,

           parameterIndex: number ) => void

          參數裝飾器顧名思義,是用來裝飾函數參數,它接收三個參數:


          target: Object - 被裝飾的類

          propertyKey: string | symbol - 方法名

          parameterIndex: number - 方法中參數的索引值

          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

          十、#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.

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


          私有字段以 # 字符開頭,有時我們稱之為私有名稱;

          每個私有字段名稱都唯一地限定于其包含的類;

          不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private);

          私有字段不能在包含的類之外訪問,甚至不能被檢測到。

          10.1 私有字段與 private 的區別

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


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

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

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


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

          那如何解決這個異常呢?當然你可以使用類型斷言把 person 轉為 any 類型:


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

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


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

             } return Person;

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

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


          class Person {

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

           }


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

           }

          }

          以上代碼目標設置為 ES2015,會編譯生成以下代碼:


          "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(this, void 0);

               __classPrivateFieldSet(this, _name, name);

             }

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

             }

          }

          _name = new WeakMap();

          通過觀察上述代碼,使用 # 號定義的 ECMAScript 私有字段,會通過 WeakMap 對象來存儲,同時編譯器會生成 __classPrivateFieldSet 和 __classPrivateFieldGet 這兩個方法用于設置值和獲取值。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合