精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

細數這些年被困擾過的 TS 問題

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

 [[340305]]

本文轉載自微信公眾號「全棧修仙之路」,作者阿寶哥 。轉載本文請聯系全棧修仙之路公眾號。  

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

TypeScript 提供最新的和不斷發展的 JavaScript 特性,包括那些來自 2015 年的 ECMAScript 和未來的提案中的特性,比如異步功能和 Decorators,以幫助建立健壯的組件。

阿寶哥第一次使用 TypeScript 是在 Angular 2.x 項目中,那時候 TypeScript 還沒有進入大眾的視野。然而現在學習 TypeScript 的小伙伴越來越多了,本文阿寶哥將分享這些年在學習 TypeScript 過程中,曾被困擾過的一些 TS 問題,希望本文對學習 TypeScript 的小伙伴能有一些幫助。

好的,下面我們來開始介紹第一個問題 —— 如何在 window 對象上顯式設置屬性。

一、如何在 window 對象上顯式設置屬性

對于使用過 JavaScript 的開發者來說,對于 window.MyNamespace = window.MyNamespace || {}; 這行代碼并不會陌生。為了避免開發過程中出現沖突,我們一般會為某些功能設置獨立的命名空間。

然而,在 TS 中對于 window.MyNamespace = window.MyNamespace || {}; 這行代碼,TS 編譯器會提示以下異常信息:

  1. Property 'MyNamespace' does not exist on type 'Window & typeof globalThis'.(2339) 

以上異常信息是說在 Window & typeof globalThis 交叉類型上不存在MyNamespace 屬性。那么如何解決這個問題呢?最簡單的方式就是使用類型斷言:

  1. (window as any).MyNamespace = {}; 

雖然使用 any 大法可以解決上述問題,但更好的方式是擴展 lib.dom.d.ts 文件中的Window 接口來解決上述問題,具體方式如下:

  1. declare interface Window { 
  2.   MyNamespace: any
  3.  
  4. window.MyNamespace = window.MyNamespace || {}; 

下面我們再來看一下 lib.dom.d.ts 文件中聲明的 Window 接口:

  1. /** 
  2.  * A window containing a DOM document; the document property  
  3.  * points to the DOM document loaded in that window.  
  4.  */ 
  5. interface Window extends EventTarget, AnimationFrameProvider, GlobalEventHandlers,  
  6.   WindowEventHandlers, WindowLocalStorage, WindowOrWorkerGlobalScope, WindowSessionStorage { 
  7.     // 已省略大部分內容 
  8.     readonly devicePixelRatio: number; 
  9.     readonly document: Document; 
  10.     readonly top: Window; 
  11.     readonly window: Window & typeof globalThis; 
  12.     addEventListener(type: string, listener: EventListenerOrEventListenerObject,  
  13.       options?: boolean | AddEventListenerOptions): void; 
  14.     removeEventListener<K extends keyof WindowEventMap>(type: K,  
  15.       listener: (this: Window, ev: WindowEventMap[K]) => any,  
  16.       options?: boolean | EventListenerOptions): void; 
  17.     [index: number]: Window; 

在上面我們聲明了兩個相同名稱的 Window 接口,這時并不會造成沖突。TypeScript 會自動進行接口合并,即把雙方的成員放到一個同名的接口中。

二、如何為對象動態分配屬性

在 JavaScript 中,我們可以很容易地為對象動態分配屬性,比如:

  1. let developer = {}; 
  2. developer.name = "semlinker"

以上代碼在 JavaScript 中可以正常運行,但在 TypeScript 中,編譯器會提示以下異常信息:

  1. Property 'name' does not exist on type '{}'.(2339) 

{} 類型表示一個沒有包含成員的對象,所以該類型沒有包含 name 屬性。為了解決這個問題,我們可以聲明一個 LooseObject 類型:

  1. interface LooseObject { 
  2.   [key: string]: any 

該類型使用 索引簽名 的形式描述 LooseObject 類型可以接受 key 類型是字符串,值的類型是 any 類型的字段。有了 LooseObject 類型之后,我們就可以通過以下方式來解決上述問題:

  1. interface LooseObject { 
  2.   [key: string]: any 
  3.  
  4. let developer: LooseObject = {}; 
  5. developer.name = "semlinker"

對于 LooseObject 類型來說,它的約束是很寬松的。在一些應用場景中,我們除了希望能支持動態的屬性之外,也希望能夠聲明一些必選和可選的屬性。

比如對于一個表示開發者的 Developer 接口來說,我們希望它的 name 屬性是必填,而 age 屬性是可選的,此外還支持動態地設置字符串類型的屬性。針對這個需求我們可以這樣做:

  1. interface Developer { 
  2.   name: string; 
  3.   age?: number; 
  4.   [key: string]: any 
  5.  
  6. let developer: Developer = { name"semlinker" }; 
  7. developer.age = 30; 
  8. developer.city = "XiaMen"

其實除了使用 索引簽名 之外,我們也可以使用 TypeScript 內置的工具類型 Record來定義 Developer 接口:

  1. // type Record<K extends string | number | symbol, T> = { [P in K]: T; } 
  2. interface Developer extends Record<string, any> { 
  3.   name: string; 
  4.   age?: number; 
  5.  
  6. let developer: Developer = { name"semlinker" }; 
  7. developer.age = 30; 
  8. developer.city = "XiaMen"

三、如何理解泛型中的 <T>

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

 

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

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

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

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

  1. function identity <T, U>(value: T, message: U) : T { 
  2.   console.log(message); 
  3.   return value; 
  4.  
  5. console.log(identity<Number, string>(68, "Semlinker")); 

 

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

  1. function identity <T, U>(value: T, message: U) : T { 
  2.   console.log(message); 
  3.   return value; 
  4.  
  5. console.log(identity(68, "Semlinker")); 

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

四、如何理解裝飾器的作用

在 TypeScript 中裝飾器分為類裝飾器、屬性裝飾器、方法裝飾器和參數裝飾器四大類。裝飾器的本質是一個函數,通過裝飾器我們可以方便地定義與對象相關的元數據。

比如在 ionic-native 項目中,它使用 Plugin 裝飾器來定義 IonicNative 中 Device 插件的相關信息:

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

在以上代碼中 Plugin 函數被稱為裝飾器工廠,調用該函數之后會返回類裝飾器,用于裝飾 Device 類。Plugin 工廠函數的定義如下:

  1. // https://github.com/ionic-team/ionic-native/blob/v3.x/src/%40ionic-native/core/decorators.ts 
  2. export function Plugin(config: PluginConfig): ClassDecorator { 
  3.   return function(cls: any) { 
  4.     // 把config對象中屬性,作為靜態屬性添加到cls類上 
  5.     for (let prop in config) { 
  6.       cls[prop] = config[prop]; 
  7.     } 
  8.  
  9.     cls['installed'] = function(printWarning?: boolean) { 
  10.       return !!getPlugin(config.pluginRef); 
  11.     }; 
  12.     // 省略其他內容 
  13.     return cls; 
  14.   }; 

通過觀察 Plugin 工廠函數的方法簽名,我們可以知道調用該函數之后會返回 ClassDecorator 類型的對象,其中 ClassDecorator 類型的聲明如下所示:

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

類裝飾器顧名思義,就是用來裝飾類的。它接收一個參數 —— target: TFunction,表示被裝飾器的類。介紹完上述內容之后,我們來看另一個問題 @Plugin({...}) 中的 @ 符號有什么用?

其實 @Plugin({...}) 中的 @ 符號只是語法糖,為什么說是語法糖呢?這里我們來看一下編譯生成的 ES5 代碼:

  1. var __decorate = (this && this.__decorate) || function (decorators, target, keydesc) { 
  2.     var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 
  3.     if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, keydesc); 
  4.     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; 
  5.     return c > 3 && r && Object.defineProperty(target, key, r), r; 
  6. }; 
  7.  
  8. var Device = /** @class */ (function (_super) { 
  9.     __extends(Device, _super); 
  10.     function Device() { 
  11.         return _super !== null && _super.apply(this, arguments) || this; 
  12.     } 
  13.     Device = __decorate([ 
  14.         Plugin({ 
  15.             pluginName: 'Device'
  16.             plugin: 'cordova-plugin-device'
  17.             pluginRef: 'device'
  18.             repo: 'https://github.com/apache/cordova-plugin-device'
  19.             platforms: ['Android''Browser''iOS''macOS''Windows'], 
  20.         }), 
  21.         Injectable() 
  22.     ], Device); 
  23.     return Device; 
  24. }(IonicNativePlugin)); 

通過生成的代碼可知,@Plugin({...}) 和 @Injectable() 最終會被轉換成普通的方法調用,它們的調用結果最終會以數組的形式作為參數傳遞給 __decorate 函數,而在 __decorate 函數內部會以 Device 類作為參數調用各自的類型裝飾器,從而擴展對應的功能。

此外,如果你有使用過 Angular,相信你對以下代碼并不會陌生。

  1. const API_URL = new InjectionToken('apiUrl'); 
  2.  
  3. @Injectable() 
  4. export class HttpService { 
  5.   constructor( 
  6.     private httpClient: HttpClient, 
  7.     @Inject(API_URL) private apiUrl: string 
  8.   ) {} 

在 Injectable 類裝飾器修飾的 HttpService 類中,我們通過構造注入的方式注入了用于處理 HTTP 請求的 HttpClient 依賴對象。而通過 Inject 參數裝飾器注入了API_URL 對應的對象,這種方式我們稱之為依賴注入(Dependency Injection)。

關于什么是依賴注入,在 TS 中如何實現依賴注入功能,出于篇幅考慮,這里阿寶哥就不繼續展開了。感興趣的小伙伴可以閱讀 “了不起的 IoC 與 DI” 這篇文章。

五、如何理解函數重載的作用

5.1 可愛又可恨的聯合類型

由于 JavaScript 是一個動態語言,我們通常會使用不同類型的參數來調用同一個函數,該函數會根據不同的參數而返回不同的類型的調用結果:

  1. function add(x, y) { 
  2.   return x + y; 
  3.  
  4. add(1, 2); // 3 
  5. add("1""2"); //"12" 

由于 TypeScript 是 JavaScript 的超集,因此以上的代碼可以直接在 TypeScript 中使用,但當 TypeScript 編譯器開啟 noImplicitAny 的配置項時,以上代碼會提示以下錯誤信息:

  1. Parameter 'x' implicitly has an 'any' type. 
  2. Parameter 'y' implicitly has an 'any' type. 

該信息告訴我們參數 x 和參數 y 隱式具有 any 類型。為了解決這個問題,我們可以為參數設置一個類型。因為我們希望 add 函數同時支持 string 和 number 類型,因此我們可以定義一個 string | number 聯合類型,同時我們為該聯合類型取個別名:

  1. type Combinable = string | number; 

在定義完 Combinable 聯合類型后,我們來更新一下 add 函數:

  1. function add(a: Combinable, b: Combinable) { 
  2.   if (typeof a === 'string' || typeof b === 'string') { 
  3.     return a.toString() + b.toString(); 
  4.   } 
  5.   return a + b; 

為 add 函數的參數顯式設置類型之后,之前錯誤的提示消息就消失了。那么此時的 add 函數就完美了么,我們來實際測試一下:

  1. const result = add('semlinker'' kakuqo'); 
  2. result.split(' '); 

在上面代碼中,我們分別使用 'semlinker' 和 ' kakuqo' 這兩個字符串作為參數調用 add 函數,并把調用結果保存到一個名為 result 的變量上,這時候我們想當然的認為此時 result 的變量的類型為 string,所以我們就可以正常調用字符串對象上的 split 方法。但這時 TypeScript 編譯器又出現以下錯誤信息了:

  1. Property 'split' does not exist on type 'Combinable'
  2. Property 'split' does not exist on type 'number'

很明顯 Combinable 和 number 類型的對象上并不存在 split 屬性。問題又來了,那如何解決呢?這時我們就可以利用 TypeScript 提供的函數重載。

5.2 函數重載

函數重載或方法重載是使用相同名稱和不同參數數量或類型創建多個方法的一種能力。

  1. function add(a: number, b: number): number; 
  2. function add(a: string, b: string): string; 
  3. function add(a: string, b: number): string; 
  4. function add(a: number, b: string): string; 
  5. function add(a: Combinable, b: Combinable) { 
  6.   // type Combinable = string | number; 
  7.   if (typeof a === 'string' || typeof b === 'string') { 
  8.     return a.toString() + b.toString(); 
  9.   } 
  10.   return a + b; 

在以上代碼中,我們為 add 函數提供了多個函數類型定義,從而實現函數的重載。在 TypeScript 中除了可以重載普通函數之外,我們還可以重載類中的成員方法。

方法重載是指在同一個類中方法同名,參數不同(參數類型不同、參數個數不同或參數個數相同時參數的先后順序不同),調用時根據實參的形式,選擇與它匹配的方法執行操作的一種技術。所以類中成員方法滿足重載的條件是:在同一個類中,方法名相同且參數列表不同。下面我們來舉一個成員方法重載的例子:

  1. class Calculator { 
  2.   add(a: number, b: number): number; 
  3.   add(a: string, b: string): string; 
  4.   add(a: string, b: number): string; 
  5.   add(a: number, b: string): string; 
  6.   add(a: Combinable, b: Combinable) { 
  7.   if (typeof a === 'string' || typeof b === 'string') { 
  8.     return a.toString() + b.toString(); 
  9.   } 
  10.     return a + b; 
  11.   } 
  12.  
  13. const calculator = new Calculator(); 
  14. const result = calculator.add('Semlinker'' Kakuqo'); 

這里需要注意的是,當 TypeScript 編譯器處理函數重載時,它會查找重載列表,嘗試使用第一個重載定義。 如果匹配的話就使用這個。 因此,在定義重載的時候,一定要把最精確的定義放在最前面。另外在 Calculator 類中,add(a: Combinable, b: Combinable){ } 并不是重載列表的一部分,因此對于 add 成員方法來說,我們只定義了四個重載方法。

六、interfaces 與 type 之間有什么區別

6.1 Objects/Functions

接口和類型別名都可以用來描述對象的形狀或函數簽名:

接口

  1. interface Point { 
  2.   x: number; 
  3.   y: number; 
  4.  
  5. interface SetPoint { 
  6.   (x: number, y: number): void; 

類型別名

  1. type Point = { 
  2.   x: number; 
  3.   y: number; 
  4. }; 
  5.  
  6. type SetPoint = (x: number, y: number) => void; 

6.2 Other Types

與接口類型不一樣,類型別名可以用于一些其他類型,比如原始類型、聯合類型和元組:

  1. // primitive 
  2. type Name = string; 
  3.  
  4. // object 
  5. type PartialPointX = { x: number; }; 
  6. type PartialPointY = { y: number; }; 
  7.  
  8. // union 
  9. type PartialPoint = PartialPointX | PartialPointY; 
  10.  
  11. // tuple 
  12. type Data = [number, string]; 

6.3 Extend

接口和類型別名都能夠被擴展,但語法有所不同。此外,接口和類型別名不是互斥的。接口可以擴展類型別名,而反過來是不行的。

Interface extends interface

  1. interface PartialPointX { x: number; } 
  2. interface Point extends PartialPointX {  
  3.   y: number;  

Type alias extends type alias

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

Interface extends type alias

  1. type PartialPointX = { x: number; }; 
  2. interface Point extends PartialPointX { y: number; } 

Type alias extends interface

  1. interface PartialPointX { x: number; } 
  2. type Point = PartialPointX & { y: number; }; 

6.4 Implements

類可以以相同的方式實現接口或類型別名,但類不能實現使用類型別名定義的聯合類型:

  1. interface Point { 
  2.   x: number; 
  3.   y: number; 
  4.  
  5. class SomePoint implements Point { 
  6.   x = 1; 
  7.   y = 2; 
  8.  
  9. type Point2 = { 
  10.   x: number; 
  11.   y: number; 
  12. }; 
  13.  
  14. class SomePoint2 implements Point2 { 
  15.   x = 1; 
  16.   y = 2; 
  17.  
  18. type PartialPoint = { x: number; } | { y: number; }; 
  19.  
  20. // A class can only implement an object type or  
  21. // intersection of object types with statically known members. 
  22. class SomePartialPoint implements PartialPoint { // Error 
  23.   x = 1; 
  24.   y = 2; 

6.5 Declaration merging

與類型別名不同,接口可以定義多次,會被自動合并為單個接口。

  1. interface Point { x: number; } 
  2. interface Point { y: number; } 
  3.  
  4. const point: Point = { x: 1, y: 2 }; 

七、object, Object 和 {} 之間有什么區別

7.1 object 類型

object 類型是:TypeScript 2.2 引入的新類型,它用于表示非原始類型。

  1. // node_modules/typescript/lib/lib.es5.d.ts 
  2. interface ObjectConstructor { 
  3.   create(o: object | null): any
  4.   // ... 
  5.  
  6. const proto = {}; 
  7.  
  8. Object.create(proto);     // OK 
  9. Object.create(null);      // OK 
  10. Object.create(undefined); // Error 
  11. Object.create(1337);      // Error 
  12. Object.create(true);      // Error 
  13. Object.create("oops");    // Error 

7.2 Object 類型

Object 類型:它是所有 Object 類的實例的類型,它由以下兩個接口來定義:

  • Object 接口定義了 Object.prototype 原型對象上的屬性;
  1. // node_modules/typescript/lib/lib.es5.d.ts 
  2. interface Object { 
  3.   constructor: Function
  4.   toString(): string; 
  5.   toLocaleString(): string; 
  6.   valueOf(): Object; 
  7.   hasOwnProperty(v: PropertyKey): boolean; 
  8.   isPrototypeOf(v: Object): boolean; 
  9.   propertyIsEnumerable(v: PropertyKey): boolean; 
  • ObjectConstructor 接口定義了 Object 類的屬性。
  1. // node_modules/typescript/lib/lib.es5.d.ts 
  2. interface ObjectConstructor { 
  3.   /** Invocation via `new` */ 
  4.   new(value?: any): Object; 
  5.   /** Invocation via function calls */ 
  6.   (value?: any): any
  7.   readonly prototype: Object; 
  8.   getPrototypeOf(o: any): any
  9.   // ··· 
  10.  
  11. declare var Object: ObjectConstructor; 

Object 類的所有實例都繼承了 Object 接口中的所有屬性。

7.3 {} 類型

{} 類型描述了一個沒有成員的對象。當你試圖訪問這樣一個對象的任意屬性時,TypeScript 會產生一個編譯時錯誤。

  1. // Type {} 
  2. const obj = {}; 
  3.  
  4. // Error: Property 'prop' does not exist on type '{}'
  5. obj.prop = "semlinker"

但是,你仍然可以使用在 Object 類型上定義的所有屬性和方法,這些屬性和方法可通過 JavaScript 的原型鏈隱式地使用:

  1. // Type {} 
  2. const obj = {}; 
  3.  
  4. // "[object Object]" 
  5. obj.toString(); 

八、數字枚舉與字符串枚舉之間有什么區別

8.1 數字枚舉

在 JavaScript 中布爾類型的變量含有有限范圍的值,即 true 和 false。而在 TypeScript 中利用枚舉,你也可以自定義相似的類型:

  1. enum NoYes { 
  2.   No
  3.   Yes, 

No 和 Yes 被稱為枚舉 NoYes 的成員。每個枚舉成員都有一個 name 和一個 value。數字枚舉成員值的默認類型是 number 類型。也就是說,每個成員的值都是一個數字:

  1. enum NoYes { 
  2.   No
  3.   Yes, 
  4.  
  5. assert.equal(NoYes.No, 0); 
  6. assert.equal(NoYes.Yes, 1); 

除了讓 TypeScript 為我們指定枚舉成員的值之外,我們還可以手動賦值:

  1. enum NoYes { 
  2.   No = 0, 
  3.   Yes = 1, 

這種通過等號的顯式賦值稱為 initializer。如果枚舉中某個成員的值使用顯式方式賦值,但后續成員未顯示賦值, TypeScript 會基于當前成員的值加 1 作為后續成員的值。

8.2 字符串枚舉

除了數字枚舉,我們還可以使用字符串作為枚舉成員值:

  1. enum NoYes { 
  2.   No = 'No'
  3.   Yes = 'Yes'
  4.  
  5. assert.equal(NoYes.No'No'); 
  6. assert.equal(NoYes.Yes, 'Yes'); 

8.3 數字枚舉 vs 字符串枚舉

數字枚舉與字符串枚舉有什么區別呢?這里我們來分別看一下數字枚舉和字符串枚舉編譯的結果:

數字枚舉編譯結果

  1. "use strict"
  2. var NoYes; 
  3. (function (NoYes) { 
  4.    NoYes[NoYes["No"] = 0] = "No"
  5.    NoYes[NoYes["Yes"] = 1] = "Yes"
  6. })(NoYes || (NoYes = {})); 

字符串枚舉編譯結果

  1. "use strict"
  2. var NoYes; 
  3. (function (NoYes) { 
  4.    NoYes["No"] = "No"
  5.    NoYes["Yes"] = "Yes"
  6. })(NoYes || (NoYes = {})); 

通過觀察以上結果,我們知道數值枚舉除了支持 從成員名稱到成員值 的普通映射之外,它還支持 從成員值到成員名稱 的反向映射。另外,對于純字符串枚舉,我們不能省略任何初始化程序。而數字枚舉如果沒有顯式設置值時,則會使用默認值進行初始化。

8.4 為數字枚舉分配越界值

講到數字枚舉,這里我們再來看個問題:

  1. const enum Fonum { 
  2.   a = 1, 
  3.   b = 2 
  4.  
  5. let value: Fonum = 12; // Ok 

相信很多讀者看到 let value: Fonum = 12; 這一行,TS 編譯器并未提示任何錯誤會感到驚訝。很明顯數字 12 并不是 Fonum 枚舉的成員。 為什么會這樣呢?我們來看一下 TypeScript issues 26362 中 DanielRosenwasser 大佬的回答:

The behavior is motivated by bitwise operations. There are times when SomeFlag.Foo | SomeFlag.Bar is intended to produce another SomeFlag. Instead you end up with number, and you don't want to have to cast back to SomeFlag.

該行為是由按位運算引起的。有時 SomeFlag.Foo | SomeFlag.Bar 用于生成另一個 SomeFlag。相反,你最終得到的是數字,并且你不想強制回退到 SomeFlag。

了解完上述內容,我們再來看一下 let value: Fonum = 12; 這個語句,該語句 TS 編譯器不會報錯,是因為數字 12 是可以通過 Fonum 已有的枚舉成員計算而得。

  1. let value: Fonum =  
  2.   Fonum.a << Fonum.b << Fonum.a |  Fonum.a << Fonum.b; // 12 

九、使用 # 定義的私有字段與 private 修飾符定義字段有什么區別

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

  1. class Person { 
  2.   #name: string; 
  3.  
  4.   constructor(name: string) { 
  5.     this.#name = name
  6.   } 
  7.  
  8.   greet() { 
  9.     console.log(`Hello, my name is ${this.#name}!`); 
  10.   } 
  11.  
  12. let semlinker = new Person("Semlinker"); 
  13.  
  14. semlinker.#name
  15. //     ~~~~~ 
  16. // Property '#name' is not accessible outside class 'Person' 
  17. // because it has a private identifier. 

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

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

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

  1. class Person { 
  2.   constructor(private name: string){} 
  3.  
  4. let person = new Person("Semlinker"); 
  5. console.log(person.name); 

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

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

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

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

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

  1. var Person = /** @class */ (function () { 
  2.     function Person(name) { 
  3.       this.name = name
  4.     } 
  5.     return Person; 
  6. }()); 
  7.  
  8. var person = new Person("Semlinker"); 
  9. console.log(person.name); 

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

  1. class Person { 
  2.   #name: string; 
  3.  
  4.   constructor(name: string) { 
  5.     this.#name = name
  6.   } 
  7.  
  8.   greet() { 
  9.     console.log(`Hello, my name is ${this.#name}!`); 
  10.   } 

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

  1. "use strict"
  2. var __classPrivateFieldSet = (this && this.__classPrivateFieldSet)  
  3.   || function (receiver, privateMap, value) { 
  4.     if (!privateMap.has(receiver)) { 
  5.       throw new TypeError("attempted to set private field on non-instance"); 
  6.     } 
  7.     privateMap.set(receiver, value); 
  8.     return value; 
  9. }; 
  10.  
  11. var __classPrivateFieldGet = (this && this.__classPrivateFieldGet)  
  12.   || function (receiver, privateMap) { 
  13.     if (!privateMap.has(receiver)) { 
  14.       throw new TypeError("attempted to get private field on non-instance"); 
  15.     } 
  16.     return privateMap.get(receiver); 
  17. }; 
  18.  
  19. var _name; 
  20. class Person { 
  21.     constructor(name) { 
  22.       _name.set(this, void 0); 
  23.       __classPrivateFieldSet(this, _name, name); 
  24.     } 
  25.     greet() { 
  26.       console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`); 
  27.     } 
  28. _name = new WeakMap(); 

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

以上提到的這些問題,相信一些小伙伴們在學習 TS 過程中也遇到了。如果有表述不清楚的地方,歡迎你們給我留言或直接與我交流。之后,阿寶哥還會繼續補充和完善這一方面的內容,感興趣的小伙伴可以一起參與喲。

十、參考資源

how-do-you-explicitly-set-a-new-property-on-window-in-typescript

how-do-i-dynamically-assign-properties-to-an-object-in-typescript

typescript-interfaces-vs-types

 

責任編輯:武曉燕 來源: 全棧修仙之路
相關推薦

2018-01-10 06:17:24

2015-10-09 11:24:16

蘋果微軟斗爭

2015-02-13 15:00:48

騰訊15年

2012-03-14 15:34:14

PaaS

2013-07-26 15:29:56

項目管理

2014-08-06 14:13:30

Windows Pho

2022-08-23 12:21:50

Linux命令

2021-09-12 22:22:15

前端

2022-05-07 23:54:59

windows操作系統應用軟件

2022-02-10 14:38:28

前端框架瀏覽器

2014-11-03 10:49:43

程序員技術

2012-02-27 15:56:14

javascript

2020-09-09 07:55:51

TS開源符號

2012-08-31 17:13:16

SuSE

2014-10-27 14:32:11

2024-05-07 08:32:45

Redis高可用映射關系

2019-11-11 22:37:35

Google收購失敗

2011-10-17 13:06:00

2021-02-05 23:29:20

人工智能機器學習技術

2021-02-06 10:30:12

人工智能
點贊
收藏

51CTO技術棧公眾號

国产精品福利片| 加勒比色综合久久久久久久久| 精品美女久久久| 亚洲女女做受ⅹxx高潮| 91av福利视频| 亚洲制服中文字幕| 国产一级特黄毛片| 国产精品久久久久久妇女| 久草精品在线观看| 亚洲精品成人久久电影| 欧美黄色免费网址| 6—12呦国产精品| 深爱激情久久| 欧美日韩亚洲一区二| 成人三级在线| 高h视频免费观看| 色噜噜成人av在线| 国产精品丝袜黑色高跟| 国产不卡精品视男人的天堂| 精品无码一区二区三区 | 六月丁香婷婷色狠狠久久| 精品国产91亚洲一区二区三区婷婷| 午夜精品视频在线观看一区二区 | av资源网在线观看| 亚洲欧美tv| 亚洲aⅴ怡春院| 国产精品视频免费观看| 久久视频免费看| 88久久精品| 亚洲一区二区三区中文字幕在线| 亚洲一区二区三区久久| 欧美黑人猛猛猛| 全球成人免费直播| 欧美另类z0zxhd电影| 伊人久久青草| 91丨九色丨丰满| 久久久久久色| 色婷婷综合久久久久| 999在线观看| 操你啦在线视频| 国产成人av资源| 91精品国产99| 久久亚洲AV无码| 亚洲女同一区| 亚洲国产精品字幕| 在线观看一区二区三区四区| 免费毛片b在线观看| 久久久久久久免费视频了| 国产精品第三页| 四虎影院在线免费播放| 色999国产精品| 精品国产sm最大网站| 91精品人妻一区二区三区四区| 第一福利在线视频| 久久精品视频一区二区| 免费日韩av电影| 国产精品久久久久久久免费看 | 在线a欧美视频| 国产欧美一区二| 国产99re66在线视频| 久久综合999| 成人午夜在线影院| 久久久久久久久久久久久av| 欧美手机视频| 一本一道久久a久久精品逆3p| 日韩乱码人妻无码中文字幕久久| 伊人久久大香线蕉| 一个人www欧美| 亚洲欧美精品久久| 任你躁在线精品免费| 欧美日韩一区二区在线观看视频| 久久久久久久9| 国产youjizz在线| 91免费视频网址| 亚洲999一在线观看www| 性欧美8khd高清极品| 肉色丝袜一区二区| 97精品在线观看| 亚洲综合视频网站| 精品国产一区二区三区噜噜噜| 亚洲午夜精品视频| 免费无码一区二区三区| 国产高清视频一区二区| 色呦呦一区二区三区| 青青草综合在线| 美女扒开腿让男人桶爽久久软| 日韩欧美高清视频| 六月婷婷激情网| 成人免费在线电影| 亚洲女人的天堂| 国产三区在线视频| 波多野结衣视频一区二区| 色噜噜狠狠成人中文综合 | 亚洲av综合一区二区| 日韩精品1区| 久久久久久久一区二区| www.xx日本| 九九久久精品| 久久夜精品香蕉| 午夜黄色福利视频| 精品视频亚洲| 欧美国产第二页| 草视频在线观看| 久久激情中文| 国产二区一区| 看女生喷水的网站在线观看| 国产精品免费视频观看| 日韩欧美精品一区二区| 国产对白叫床清晰在线播放| 亚洲主播在线播放| 8x8ⅹ国产精品一区二区二区| 日韩欧美一中文字暮专区| 欧美剧情片在线观看| 人体私拍套图hdxxxx| 91精品啪在线观看国产81旧版| 中文字幕精品国产| 99自拍偷拍视频| 亚洲免费成人| 91高清免费视频| 国产999久久久| 高清不卡一区二区在线| 亚洲一卡二卡区| 日本在线免费中文字幕| 日韩理论片一区二区| 色哺乳xxxxhd奶水米仓惠香| 日韩成人影音| 欧美性大战xxxxx久久久| 亚洲欧美自拍另类日韩| av国产精品| 欧美刺激午夜性久久久久久久| 国产精品久久久久久亚洲色| 日韩精品导航| 一区二区亚洲欧洲国产日韩| www.国产高清| 日本强好片久久久久久aaa| 国产精品电影一区| 欧美视频综合| 综合网在线视频| www.av片| 成人精品一区二区三区电影| 精品在线欧美视频| www.黄色com| 久久久蜜桃一区二区人| 免费毛片一区二区三区久久久| 国产在线精彩视频| 亚洲国语精品自产拍在线观看| 精品视频久久久久| 国产成人av影院| 一本久道高清无码视频| 成人软件在线观看| 日韩欧美在线不卡| wwwwxxxx国产| 狂野欧美性猛交xxxx巴西| 成人在线视频网站| 丰满人妻av一区二区三区| 久久综合网色—综合色88| 日本网站免费在线观看| 国产精品伦一区二区| 中文字幕综合一区| 国产精品高潮呻吟av| 亚洲啪啪综合av一区二区三区| 亚洲一级片免费观看| 天堂av一区二区三区在线播放| 69视频在线播放| 黄色国产在线| 亚洲永久精品国产| 中文字幕 欧美 日韩| 精品美女在线视频| 国产精品一二三视频| 香蕉国产在线视频| 亚洲色图20p| 性折磨bdsm欧美激情另类| 亚洲国内精品| 91欧美视频网站| 精品一性一色一乱农村| 欧美日韩大陆一区二区| 在线不卡av电影| 久久精品国产亚洲高清剧情介绍| 强伦女教师2:伦理在线观看| 自拍偷自拍亚洲精品被多人伦好爽| 亚洲视频一区二区三区| 日韩美女黄色片| 国产精品亚洲а∨天堂免在线| 色播亚洲婷婷| 国产精品麻豆| 欧美亚洲国产成人精品| 丰满熟妇人妻中文字幕| 欧美日韩免费在线| 小嫩苞一区二区三区| 99久久精品99国产精品| 无码人妻精品一区二区蜜桃网站| 麻豆精品少妇| 国产精品小说在线| 成av人片在线观看www| 色婷婷成人综合| 天天操天天操天天干| 亚洲午夜av在线| 五月天精品视频| 亚洲视频一区| 99在线观看| 在线黄色网页| 欧美不卡在线视频| 免费无遮挡无码永久在线观看视频| 激情欧美一区二区| 国产麻豆电影在线观看| 激情av综合| 成人免费福利在线| 欧美7777| 91豆花精品一区| 少女频道在线观看高清 | 亚洲乱码国产乱码精品精软件| 国产精品三级电影| 亚洲一级av无码毛片精品| 国产在线精品一区在线观看麻豆| 综合国产精品久久久| 亚洲a级精品| 成人av资源| 国产精久久一区二区| 国产精品久久久久久久久久东京| 成人免费图片免费观看| 九九精品视频在线观看| 亚洲美女综合网| 在线电影院国产精品| 国产情侣小视频| 国产精品国产自产拍在线| 午夜av中文字幕| 轻轻草成人在线| av片中文字幕| 先锋影音国产一区| 亚洲欧洲精品在线| 老司机亚洲精品一区二区| 欧美成人h版在线观看| wwwav网站| 欧美精品久久久久久久多人混战 | 韩国一区二区在线观看| 天堂社区在线视频| 91精品啪在线观看国产81旧版| 色一情一乱一伦一区二区三区丨 | 成人性生交大片免费看中文网站| 欧美精品久久久久久久自慰| 伊人色**天天综合婷婷| 亚洲自拍偷拍一区二区三区| 999国产精品| 国产精品一区二区三区不卡| 综合视频一区| 岛国一区二区三区高清视频| jizz性欧美23| 国产精品一二区| 亚洲狼人综合| 91网站在线看| 亚洲日本视频在线| 高清日韩一区| 欧美黄色影院| 日本视频一区二区不卡| 看亚洲a级一级毛片| 亚洲伊人一本大道中文字幕| 久久天堂久久| 成人在线免费观看一区| 啪啪激情综合网| 奇米影视首页 狠狠色丁香婷婷久久综合| 九九视频精品全部免费播放| 日韩欧美一区二区三区久久婷婷| 日韩精品免费一区二区在线观看| 一区二区三区欧美成人| 欧美精品啪啪| 亚洲欧洲精品一区二区三区波多野1战4 | 91精品国产色综合久久不卡电影 | 欧美日韩国产综合久久| 国产日韩欧美中文字幕| 色悠久久久久综合欧美99| 亚洲高清在线看| 欧美卡1卡2卡| 天堂在线观看免费视频| 国产亚洲欧洲高清一区| 国产高清一区二区三区视频| 久久免费国产精品1| 韩国中文字幕在线| 久久久久久久久久国产| 欲香欲色天天天综合和网| 欧美激情成人在线视频| 国产自产自拍视频在线观看| 国产精品444| 亚洲国产视频二区| 蜜桃999成人看片在线观看| 国产精品麻豆久久| 亚洲精品久久区二区三区蜜桃臀| 91超碰国产精品| 久久久噜噜噜www成人网| 黄一区二区三区| 一二三不卡视频| 99re亚洲国产精品| 精品国产大片大片大片| 午夜电影久久久| 国产又粗又猛又爽又黄视频| 日韩电影中文字幕| 午夜性色福利视频| xxxxxxxxx欧美| 欧美极品视频| www.欧美精品| 男女羞羞在线观看| 91久久久精品| 欧美亚洲精品在线| 久色视频在线播放| 国产精品一区专区| 亚洲一级片在线播放| 中文字幕电影一区| 日本在线免费观看| 欧美一卡二卡在线| a中文在线播放| 91av视频在线观看| 欧美午夜在线播放| 91视频99| 中文在线免费一区三区| 亚洲精品乱码视频| 鲁大师成人一区二区三区| 欧洲熟妇的性久久久久久| 中文字幕一区二区三区四区不卡| 国产精品suv一区| 色婷婷av一区二区三区之一色屋| wwwav在线播放| 久久精品视频亚洲| 美女网站视频在线| 91九色单男在线观看| 日韩成人精品一区二区| 男人操女人免费| 久久久亚洲国产美女国产盗摄| 国产无套在线观看| 欧美va亚洲va在线观看蝴蝶网| 麻豆电影在线播放| 成人av在线网址| 99视频精品视频高清免费| 9久久婷婷国产综合精品性色| 久久日韩精品一区二区五区| 亚洲男人第一av| 日韩电影免费观看中文字幕| av老司机在线观看| 国产精品jizz在线观看麻豆| 亚州av一区| 男人天堂网视频| 久久久久久久av麻豆果冻| 可以免费看的av毛片| 亚洲激情免费观看| 日产福利视频在线观看| 精品视频一区在线| 日本久久黄色| 男女污污的视频| 国产精品香蕉一区二区三区| 东方av正在进入| 日韩欧美国产高清91| 青青草av免费在线观看| 久久国产精品首页| 国产人与zoxxxx另类91| 亚洲天堂第一区| 日韩精品高清不卡| 日本美女bbw| 欧美精品乱人伦久久久久久| a视频在线观看| 国产成人精品福利一区二区三区| 伊人精品在线| 亚洲精品久久久中文字幕| 国产精品麻豆久久久| 国产成人av免费看| 午夜精品在线视频| 国产99亚洲| 成人免费网站入口| av在线不卡免费看| 欧美性猛交xxxxx少妇| 亚洲成人网在线| 黄色成人影院| 丁香五月网久久综合| 蘑菇福利视频一区播放| 国产馆在线观看| 精品久久99ma| 搜成人激情视频| 日本丰满大乳奶| 久久伊99综合婷婷久久伊| 亚洲天堂avav| 亚洲色图国产精品| 99久久这里有精品| 欧美亚洲日本一区二区三区| 国产精品18久久久久久久久| 国产精品18在线| 精品免费国产二区三区| 欧美黄色网页| 四虎精品欧美一区二区免费| av网站免费线看精品| 一级片一区二区三区| 2019亚洲日韩新视频| 久久久久久久久久久久久久久久久久 | 欧美性受xxxx黑人猛交88| 99久久精品一区| 911美女片黄在线观看游戏| 午夜精品久久久久久久白皮肤 | 欧洲视频一区| 久久久久99人妻一区二区三区 | 99在线精品视频免费观看20| 欧美在线视频一二三| 日韩母乳在线| 午夜视频在线网站| 色婷婷综合久久久久中文|