typeScript
- 1.什么是TypeScript?
- 是什么?
- 特性?
- 區別?
- 2.TypeScript數據類型?
- 3.說說你對 TypeScript 中枚舉類型的理解?應用場景?
- 4.說說你對 TypeScript 中接口的理解?應用場景?
- 使用方法
- 5.說說你對 TypeScript 中類的理解?應用場景?
- 使用方法
- 6.修飾符
- 私有修飾符
- 受保護修飾符
- 靜態屬性
- 7.抽象類
- 8.說說你對 TypeScript 中函數的理解?與 JavaScript 函數的區別?
- 使用方式
- 可選參數
- 剩余類型
- 函數重載
- 區別
- 9.說說你對 TypeScript 中泛型的理解?應用場景?
- 使用方式
- 函數聲明
- 接口聲明
- 10.說說你對 TypeScript 中高級類型的理解?有哪些?
- 交叉類型
- 聯合類型
- 類型別名
- 類型索引
- 類型約束
- 映射類型
- 條件類型
- 11.說說你對 TypeScript 裝飾器的理解?應用場景?
- 使用方式
- 類裝飾
- 方法/屬性裝飾
- 參數裝飾
- 訪問器裝飾
- 裝飾器工廠
- 執行順序
- 12.說說對 TypeScript 中命名空間與模塊的理解?區別?
- 13.typescript 中的 is 關鍵字有什么用?
- 14.ts中any和unknown有什么區別?
- 總結
- 15.如何將 unknown 類型指定為一個更具體的類型?
- 16.類型斷言
- 17.void和never區別
- 18.函數如何聲明數據類型
- 19.tscongfig.json作用
- 20.如何檢測null和undefined
- 21.非空斷言
- 22.ts對象混入
- 23.type和interface區別
- 24.ts變量及變量提升
- 25.ts裝飾器
- 26.ts混入
- 27.封裝map方法
1.什么是TypeScript?
是什么?
TypeScript
是 JavaScript
的類型的超集,支持ES6
語法,支持面向對象編程的概念,如類、接口、繼承、泛型等 。其是一種靜態類型檢查的語言,提供了類型注解,在代碼編譯階段就可以檢查出數據類型的錯誤
同時擴展了JavaScript
的語法,所以任何現有的JavaScript
程序可以不加改變的在 TypeScript
下工作,為了保證兼容性,TypeScript
在編譯階段需要編譯器編譯成純 JavaScript
來運行
特性?
- 類型批注和編譯時類型檢查 :在編譯時批注變量類型
- 類型推斷:ts 中沒有批注變量類型會自動推斷變量的類型
- 類型擦除:在編譯過程中批注的內容和接口會在運行時利用工具擦除
- 接口:ts 中用接口來定義對象類型
- 枚舉:用于取值被限定在一定范圍內的場景
- Mixin:可以接受任意類型的值
- 泛型編程:寫代碼時使用一些以后才指定的類型
- 名字空間:名字只在該區域內有效,其他區域可重復使用該名字而不沖突
- 元組:元組合并了不同類型的對象,相當于一個可以裝不同類型數據的數組
區別?
- TypeScript 是 JavaScript 的超集,擴展了 JavaScript 的語法
- TypeScript 可處理已有的 JavaScript 代碼,并只對其中的 TypeScript 代碼進行編譯
- TypeScript 文件的后綴名 .ts (.ts,.tsx,.dts),JavaScript 文件是 .js
- 在編寫 TypeScript 的文件的時候就會自動編譯成 js 文件
2.TypeScript數據類型?
-
當然,請看下面的例子:
- boolean(布爾類型): 表示邏輯值,可以是 true 或 false。用于表示條件的真假或開關狀態。
let isTrue: boolean = true; let isFalse: boolean = false;
- number(數字類型): 表示數值,包括整數和浮點數。可用于表示計數、金額、坐標等數值數據。
let count: number = 10; let pi: number = 3.14;
- string(字符串類型): 表示文本數據,由字符組成的序列。用于存儲和操作文本信息,如名稱、描述、消息等。
let message: string = "Hello, TypeScript!"; let name: string = "John Doe";
- array(數組類型): 表示一個元素的集合,并且同一數組中的元素具有相同的類型。常用于存儲列表、集合或一組相關數據
let numbers: number[] = [1, 2, 3, 4, 5]; let names: string[] = ["Alice", "Bob", "Charlie"];
- tuple(元組類型): 表示固定長度和類型的數組,各個元素的類型不必相同。用于存儲具有固定位置和含義的數據集合。
let person: [string, number] = ["John Doe", 30];
- enum(枚舉類型): 用于定義一組具名的常量值,方便使用這些常量的名稱而不是硬編碼的值。用于表示具有離散取值的情況,比如顏色選項、狀態類型等。
enum Color {Red,Green,Blue, }let myColor: Color = Color.Red;
- any(任意類型): 表示所有可能的類型都可以賦值給它,它關閉了編譯時類型檢查,可靈活處理不確定類型的情況。適用于需要動態類型或與現有代碼兼容性要求較高的情況
let value: any = 123; value = "hello"; value = true;
- null 和 undefined 類型: 表示變量可以為 null 或 undefined,通常用于表示一個值缺失或未賦值的狀態。
let nullValue: null = null; let undefinedValue: undefined = undefined;
- void 類型: 用于標識方法返回值的類型,表示該方法沒有返回值。常用于定義沒有返回值的函數或方法。
function greet(): void {console.log("Hello!"); }
- never 類型: 表示那些永遠不會出現的類型,通常用于函數永不返回和拋出異常的場景。
function throwError(message: string): never {throw new Error(message); }
- object 對象類型: 表示非原始類型的對象,如數組、函數、類等。可用于存儲和操作復雜結構的數據。
let person: object = {name: "John Doe",age: 30, };
這些是對每種類型的簡單舉例,可以根據需要進行使用和擴展。
3.說說你對 TypeScript 中枚舉類型的理解?應用場景?
在TypeScript中,枚舉類型用于表示一組具名的常量值。它們允許我們定義一組有序、離散的值,并為這些值分配有意義的名稱。
枚舉類型的語法如下:
enum Color {Red,Green,Blue,
}
在這個例子中,Color 是一個枚舉類型,它包含了三個值:Red、Green、Blue。默認情況下,枚舉成員從0開始自動遞增,所以 Red 的值為0,Green 的值為1,Blue 的值為2。
枚舉類型在以下情況下非常有用:
- 表示一組相關的常量: 枚舉可以用來表示一組相關的常量,例如表示顏色、星期幾、方向等。通過使用枚舉,提供了更明確和可讀性更高的代碼。
- 避免使用魔法數值: 使用枚舉可以避免直接在代碼中使用魔法數值(未經解釋的硬編碼數字),使代碼更具可維護性。
- 枚舉成員的值可以自定義: 枚舉成員的值不僅可以自動遞增,還可以手動賦值。這樣可以靈活地指定特定的值,滿足特定的需求。
- 支持反向映射: 枚舉類型是雙向映射的,即可以根據枚舉成員的名稱獲取對應的值,也可以根據值獲取對應的枚舉成員名稱。這在某些情況下非常有用,例如根據顏色代碼獲取顏色名稱。
示例代碼:
enum Color {Red = '#FF0000',Green = '#00FF00',Blue = '#0000FF',
}console.log(Color.Red); // 輸出:#FF0000
console.log(Color[0]); // 輸出:Red
需要注意的是,枚舉類型的值只在運行時有意義,編譯后的JavaScript代碼會將枚舉轉換為普通的對象,不會像TypeScript中那樣使用特定的枚舉類型。
總而言之,枚舉類型在TypeScript中用于定義一組具名的常量值,可提高代碼的可讀性和可維護性,并支持自定義值和反向映射。它們在表示離散取值的場景下非常有用。
4.說說你對 TypeScript 中接口的理解?應用場景?
個接口所描述的是一個對象相關的屬性和方法,但并不提供具體創建此對象實例的方法typescript
的核心功能之一就是對類型做檢測,
使用方法
interface User {name: stringage?: number//可選參數readonly isMale: boolean//只讀參數say: (words: string) => string//函數
}
const fn = (user: User) => user.namefn(name:'lisi')
接口還可以繼承
interface Father {color: String
}interface Mother {height: Number
}interface Son extends Father,Mother{name: stringage: Number
}
5.說說你對 TypeScript 中類的理解?應用場景?
在 ES6
之后,JavaScript
擁有了 class
關鍵字,雖然本質依然是構造函數,但是使用起來已經方便了許多,但是JavaScript
的class
依然有一些特性還沒有加入,比如修飾符和抽象類,TypeScript
的 class
支持面向對象的所有特性,比如 類、接口等
使用方法
定義類的關鍵字為 class
,后面緊跟類名,類可以包含以下幾個模塊(類的數據成員):
- 字段 : 字段是類里面聲明的變量。字段表示對象的有關數據。
- 構造函數: 類實例化時調用,可以為類的對象分配內存。
- 方法: 方法為對象要執行的操作
class Car {// 字段engine:string;// 構造函數constructor(engine:string) {this.engine = engine}// 方法disp():void {console.log("發動機為 : "+this.engine)}
}
繼承
class Animal {move(distanceInMeters: number = 0) {console.log(`Animal moved${distanceInMeters}m.`);}
}class Dog extends Animal {bark() {console.log('Woof! Woof!');}
}const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
Dog
是一個 派生類,它派生自 Animal
基類,派生類通常被稱作子類,基類通常被稱作 超類
Dog
類繼承了Animal
類,因此實例dog
也能夠使用Animal
類move
方法
同樣,類繼承后,子類可以對父類的方法重新定義,這個過程稱之為方法的重寫,通過super
關鍵字是對父類的直接引用,該關鍵字可以引用父類的屬性和方法,
class PrinterClass {doPrint():void {console.log("父類的 doPrint() 方法。")}
}class StringPrinter extends PrinterClass {doPrint():void {super.doPrint() // 調用父類的函數console.log("子類的 doPrint()方法。")}
}
6.修飾符
- 公共 public:可以自由的訪問類程序里定義的成員
- 私有 private:只能夠在該類的內部進行訪問
- 受保護 protect:除了在該類的內部可以訪問,還可以在子類中仍然可以訪問
- 只讀修飾符readonly:表示屬性只能在創建對象時或構造函數中進行賦值,之后不能修改。
- 靜態屬性static:表示屬性或方法屬于類本身而不是類的實例。可以通過類名直接訪問靜態成員,無需實例化對象。
私有修飾符
只能夠在該類的內部進行訪問,實例對象并不能夠訪問
class Father {private name: Stringconstructor(name: String) {this.name = name}
}
const father = new Father('huihui')
//屬性“name”為私有屬性,只能在類“Father"中訪問。 ts(2341)(property) Father.name: String
father.name
并且繼承該類的子類并不能訪問
class Father {private name: Stringconstructor(name: String) {this.name = name}
}class Son extends Father{say() {console.log('my name is ${this.name}')//獲取不到name}
受保護修飾符
跟私有修飾符很相似,實例對象同樣不能訪問受保護的屬性
class Father{protected name: Stringconstructor(name: String) {this.name = name}
}
const father = new Father('huihui')
father.name//name獲取不到
在子類中可以獲取到
class Father {protected name: Stringconstructor(name: String) {this.name = name}
}
class Son extends Fatherfsay() {say(){console.log('my name is $(this.name')//可以獲取到}
}
靜態屬性
這些屬性存在于類本身上面而不是類的實例上,通過static
進行定義,訪問這些屬性需要通過 類型.靜態屬性 的這種形式訪問
class Square {static width = '100px'
}console.log(Square.width) // 100px
7.抽象類
抽象類做為其它派生類的基類使用,它們一般不會直接被實例化,不同于接口,抽象類可以包含成員的實現細節,abstract
關鍵字是用于定義抽象類和在抽象類內部定義抽象方法,
abstract class Animal {abstract makeSound(): void;move(): void {console.log('roaming the earch...');}
}
這種類并不能被實例化,通常需要我們創建子類去繼承
class Cat extends Animal {makeSound() {console.log('miao miao')}
}const cat = new Cat()cat.makeSound() // miao miao
cat.move() // roaming the earch...
8.說說你對 TypeScript 中函數的理解?與 JavaScript 函數的區別?
函數是JavaScript
應用程序的基礎,幫助我們實現抽象層、模擬類、信息隱藏和模塊,在TypeScript
里,雖然已經支持類、命名空間和模塊,但函數仍然是主要定義行為的方式,TypeScript
為 JavaScript
函數添加了額外的功能,豐富了更多的應用場景
使用方式
跟javascript
定義函數十分相似,可以通過funciton
關鍵字、箭頭函數等形式去定
const add = (a: number, b: number) => a + b
當我們沒有提供函數實現的情況下,有兩種聲明函數類型的方式
// 方式一
type LongHand = {(a: number): number;
};// 方式二
type ShortHand = (a: number) => number;
可選參數
當函數的參數可能是不存在的,只需要在參數后面加上 ?
代表參數可能不存在,
const add = (a: number, b?: number) => a + (b ? b : 0)
剩余類型
剩余參數與JavaScript
的語法類似,需要用 ...
來表示剩余參數
如果剩余參數 rest
是一個由number
類型組成的數組
const add = (a: number, ...rest: number[]) => rest.reduce(((a, b) => a + b), a)
函數重載
允許創建數項名稱相同但輸入輸出類型或個數不同的子程序,它可以簡單地稱為一個單獨功能可以執行多項任務的能力
關于typescript
函數重載,必須要把精確的定義放在前面,最后函數實現時,需要使用 |
操作符或者?
操作符,把所有可能的輸入類型全部包含進去
function add (arg1: string, arg2: string): string
function add (arg1: number, arg2: number): number
// 因為我們在下邊有具體函數的實現,所以這里并不需要添加 declare 關鍵字// 下邊是實現
function add (arg1: string | number, arg2: string | number) {// 在實現上我們要注意嚴格判斷兩個參數的類型是否相等,而不能簡單的寫一個 arg1 + arg2if (typeof arg1 === 'string' && typeof arg2 === 'string') {return arg1 + arg2} else if (typeof arg1 === 'number' && typeof arg2 === 'number') {return arg1 + arg2}
}
區別
- 從定義的方式而言,typescript 聲明函數需要定義參數類型或者聲明返回值類型
- typescript 在參數中,添加可選參數供使用者選擇
- typescript 增添函數重載功能,使用者只需要通過查看函數聲明的方式,即可知道函數傳遞的參數個數以及類型
9.說說你對 TypeScript 中泛型的理解?應用場景?
泛型程序設計(generic programming)是程序設計語言的一種風格或范式,泛型允許我們在強類型程序設計語言中編寫代碼時使用一些以后才指定的類型,在實例化時作為參數指明這些類型 在typescript
中,定義函數,接口或者類的時候,不預先定義好具體的類型,而在使用的時候在指定類型的一種特性
假設我們用一個函數,它可接受一個 number
參數并返回一個number
參數,如下寫法:
function returnItem (para: number): number {return para
}
如果我們打算接受一個 string
類型,然后再返回 string
類型,則如下寫法:
function returnItem (para: string): string {return para
}
上述兩種編寫方式,存在一個最明顯的問題在于,代碼重復度比較高
雖然可以使用 any
類型去替代,但這也并不是很好的方案,因為我們的目的是接收什么類型的參數返回什么類型的參數,即在運行時傳入參數我們才能確定類型,這種情況就可以使用泛型,
function returnItem<T>(para: T): T {return para
}
使用方式
泛型通過<>
的形式進行表述,可以聲明:
- 函數
- 接口
- 類
函數聲明
聲明函數的形式如下:
function returnItem<T>(para: T): T {return para
}
定義泛型的時候,可以一次定義多個類型參數,比如我們可以同時定義泛型 T
和 泛型 U
:
function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], tuple[0]];
}swap([7, 'seven']); // ['seven', 7]
接口聲明
聲明接口的形式如下:
interface ReturnItemFn<T> {(para: T): T
}
那么當我們想傳入一個number作為參數的時候,就可以這樣聲明函數:
const returnItem: ReturnItemFn<number> = para => para
10.說說你對 TypeScript 中高級類型的理解?有哪些?
除了string
、number
、boolean
這種基礎類型外,在 typescript
類型聲明中還存在一些高級的類型應用
這些高級類型,是typescript
為了保證語言的靈活性,所使用的一些語言特性。這些特性有助于我們應對復雜多變的開發場景
常見的高級類型有如下:
- 交叉類型
- 聯合類型
- 類型別名
- 類型索引
- 類型約束
- 映射類型
- 條件類型
交叉類型
通過 &
將多個類型合并為一個類型,包含了所需的所有類型的特性,本質上是一種并的操作
T & U
聯合類型
聯合類型的語法規則和邏輯 “或” 的符號一致,表示其類型為連接的多個類型中的任意一個,本質上是一個交的關系
T | U
類型別名
類型別名會給一個類型起個新名字,類型別名有時和接口很像,但是可以作用于原始值、聯合類型、元組以及其它任何你需要手寫的類型
可以使用 type SomeName = someValidTypeAnnotation
的語法來創建類型別名:
type some = boolean | stringconst b: some = true // ok
const c: some = 'hello' // ok
const d: some = 123 // 不能將類型“123”分配給類型“some”
此外類型別名可以是泛型:
type Container<T> = { value: T };
也可以使用類型別名來在屬性里引用自己:
type Tree<T> = {value: T;left: Tree<T>;right: Tree<T>;
}
可以看到,類型別名和接口使用十分相似,都可以描述一個對象或者函數
兩者最大的區別在于,interface
只能用于定義對象類型,而 type
的聲明方式除了對象之外還可以定義交叉、聯合、原始類型等,類型聲明的方式適用范圍顯然更加廣泛
類型索引
keyof
類似于 Object.keys
,用于獲取一個接口中 Key 的聯合類型。
interface Button {type: stringtext: string
}type ButtonKeys = keyof Button
// 等效于
type ButtonKeys = "type" | "text"
類型約束
通過關鍵字 extend
進行約束,不同于在 class
后使用 extends
的繼承作用,泛型內使用的主要作用是對泛型加以約束
type BaseType = string | number | boolean// 這里表示 copy 的參數
// 只能是字符串、數字、布爾這幾種基礎類型
function copy<T extends BaseType>(arg: T): T {return arg
}
類型約束通常和類型索引一起使用,例如我們有一個方法專門用來獲取對象的值,但是這個對象并不確定,我們就可以使用 extends
和 keyof
進行約束。
function getValue<T, K extends keyof T>(obj: T, key: K) {return obj[key]
}const obj = { a: 1 }
const a = getValue(obj, 'a')
映射類型
通過 in
關鍵字做類型的映射,遍歷已有接口的 key
或者是遍歷聯合類型,如下例子:
type Readonly<T> = {readonly [P in keyof T]: T[P];
};interface Obj {a: stringb: string
}type ReadOnlyObj = Readonly<Obj>
上述的結構,可以分成這些步驟:
- keyof T:通過類型索引 keyof 的得到聯合類型 ‘a’ | ‘b’
- P in keyof T 等同于 p in ‘a’ | ‘b’,相當于執行了一次 forEach 的邏輯,遍歷 ‘a’ | ‘b’
所以最終ReadOnlyObj
的接口為下述:
interface ReadOnlyObj {readonly a: string;readonly b: string;
}
條件類型
條件類型的語法規則和三元表達式一致,經常用于一些類型不確定的情況。
T extends U ? X : Y
上面的意思就是,如果 T 是 U 的子集,就是類型 X,否則為類型 Y
11.說說你對 TypeScript 裝飾器的理解?應用場景?
裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法, 訪問符,屬性或參數上
是一種在不改變原類和使用繼承的情況下,動態地擴展對象功能
同樣的,本質也不是什么高大上的結構,就是一個普通的函數,@expression
的形式其實是Object.defineProperty
的語法糖
expression
求值后必須也是一個函數,它會在運行時被調用,被裝飾的聲明信息做為參數傳入
使用方式
由于typescript
是一個實驗性特性,若要使用,需要在tsconfig.json
文件啟動,如下:
{"compilerOptions": {"target": "ES5","experimentalDecorators": true}
}
typescript
裝飾器的使用和javascript
基本一致
類的裝飾器可以裝飾:
- 類
- 方法/屬性
- 參數
- 訪問器
類裝飾
例如聲明一個函數 addAge
去給 Class 的屬性 age
添加年齡.
function addAge(constructor: Function) {constructor.prototype.age = 18;
}@addAge
class Person{name: string;age!: number;constructor() {this.name = 'huihui';}
}let person = new Person();console.log(person.age); // 18
上述代碼,實際等同于以下形式:
Person = addAge(function Person() { ... });
上述可以看到,當裝飾器作為修飾類的時候,會把構造器傳遞進去。 constructor.prototype.age
就是在每一個實例化對象上面添加一個 age
屬性
方法/屬性裝飾
同樣,裝飾器可以用于修飾類的方法,這時候裝飾器函數接收的參數變成了:
- target:對象的原型
- propertyKey:方法的名稱
- descriptor:方法的屬性描述符
可以看到,這三個屬性實際就是Object.defineProperty
的三個參數,如果是類的屬性,則沒有傳遞第三個參數
如下例子:
// 聲明裝飾器修飾方法/屬性
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {console.log(target);console.log("prop " + propertyKey);console.log("desc " + JSON.stringify(descriptor) + "\n\n");descriptor.writable = false;
};function property(target: any, propertyKey: string) {console.log("target", target)console.log("propertyKey", propertyKey)
}class Person{@propertyname: string;constructor() {this.name = 'huihui';}@methodsay(){return 'instance method';}@methodstatic run(){return 'static method';}
}const xmz = new Person();// 修改實例方法say
xmz.say = function() {return 'edit'
輸出如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-88tgDVu0-1692176481624)(C:\Users\l\AppData\Roaming\Typora\typora-user-images\1691724716880.png)]
參數裝飾
接收3個參數,分別是:
- target :當前對象的原型
- propertyKey :參數的名稱
- index:參數數組中的位置
function logParameter(target: Object, propertyName: string, index: number) {console.log(target);console.log(propertyName);console.log(index);
}class Employee {greet(@logParameter message: string): string {return `hello ${message}`;}
}
const emp = new Employee();
emp.greet('hello');
輸入如下圖:
訪問器裝飾
使用起來方式與方法裝飾一致,如下:
function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {console.log(target);console.log("prop " + propertyKey);console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};class Person{_name: string;constructor() {this._name = 'huihui';}@modificationget name() {return this._name}
}
裝飾器工廠
如果想要傳遞參數,使裝飾器變成類似工廠函數,只需要在裝飾器函數內部再函數一個函數即可,如下:
function addAge(age: number) {return function(constructor: Function) {constructor.prototype.age = age}
}@addAge(10)
class Person{name: string;age!: number;constructor() {this.name = 'huihui';}
}let person = new Person();
執行順序
當多個裝飾器應用于一個聲明上,將由上至下依次對裝飾器表達式求值,求值的結果會被當作函數,由下至上依次調用,例如如下:
function f() {console.log("f(): evaluated");return function (target, propertyKey: string, descriptor: PropertyDescriptor) {console.log("f(): called");}
}function g() {console.log("g(): evaluated");return function (target, propertyKey: string, descriptor: PropertyDescriptor) {console.log("g(): called");}
}class C {@f()@g()method() {}
}// 輸出
f(): evaluated
g(): evaluated
g(): called
f(): called
12.說說對 TypeScript 中命名空間與模塊的理解?區別?
ypeScript
與ECMAScript
2015 一樣,任何包含頂級 import
或者 export
的文件都被當成一個模塊
相反地,如果一個文件不帶有頂級的import
或者export
聲明,那么它的內容被視為全局可見的
例如我們在在一個 TypeScript
工程下建立一個文件 1.ts
,聲明一個變量a
const a = 1
然后在另一個文件同樣聲明一個變量a
,這時候會出現錯誤信息
提示重復聲明a
變量,但是所處的空間是全局的
如果需要解決這個問題,則通過import
或者export
引入模塊系統即可,如下:
const a = 10;export default a
在typescript
中,export
關鍵字可以導出變量或者類型,用法與es6
模塊一致,如下:
export const a = 1
export type Person = {name: String
}
通過import
引入模塊,如下:
import { a, Person } from './export';
13.typescript 中的 is 關鍵字有什么用?
TypeScript 中的 is
關鍵字用于類型保護,可以在運行時判斷一個對象是否屬于某個類型,并根據不同的類型執行不同的邏輯。
具體來說,is
關鍵字通常和 instanceof
運算符一起使用,用于判斷一個對象是否是某個類的實例。
14.ts中any和unknown有什么區別?
unknown 和 any 的主要區別是 unknown 類型會更加嚴格:在對 unknown 類型的值執行大多數操作之前,我們必須進行某種形式的檢查。而在對 any 類型的值執行操作之前,我們不必進行任何檢查。
let foo: any = 123;
console.log(foo.msg); // 符合TS的語法
let a_value1: unknown = foo; // OK
let a_value2: any = foo; // OK
let a_value3: string = foo; // OKlet bar: unknown = 222; // OK
console.log(bar.msg); // Error
let k_value1: unknown = bar; // OK
let K_value2: any = bar; // OK
let K_value3: string = bar; // Error
因為bar是一個未知類型(任何類型的數據都可以賦給 unknown
類型),所以不能確定是否有msg屬性。不能通過TS語法檢測;而 unknown 類型的值也不能將值賦給 any 和 unknown 之外的類型變量
總結
any 和 unknown 都是頂級類型,但是 unknown 更加嚴格,不像 any 那樣不做類型檢查,反而 unknown 因為未知性質,不允許訪問屬性,不允許賦值給其他有明確類型的變量。unknown 不破壞其他類型
15.如何將 unknown 類型指定為一個更具體的類型?
- 使用 typeof 進行類型判斷(這些縮小類型范圍的技術都有助于TS基于控制流程下的類型分析)
1 function unknownToString(value: unknown): string {
2 if (typeof value === "string") {
3 return value;
4 }
5
6 return String(value);
7 }
- 對 unknown 類型使用類型斷言
要強制編譯器信任類型為 unknown 的值為給定類型,則可以使用類型斷言:
1 const value: unknown = "Hello World";
2 const foo: string = value; // Error
3 const bar: string = value as string; // OK
斷言錯了時語法能通過檢測,但是運行的時候就會報錯了!
1 const value: unknown = "Hello World";
2
3 const bar: number = value as number; // runtime Error
16.類型斷言
在TypeScript中,類型斷言(Type Assertion)可以用來告訴編譯器某個值的確切類型。它類似于其他編程語言中的類型轉換,但在運行時不會對數據進行任何轉換或檢查。
在TypeScript中,有兩種形式的類型斷言:
- 尖括號語法(Angle Bracket Syntax):
let someValue: any = "hello world";
let strLength: number = (<string>someValue).length;
在上面的例子中,我們使用尖括號語法將someValue
斷言為string
類型,并賦給strLength
變量。這樣,在編譯時就能通過類型檢查,可以安全地獲取字符串長度。
- as 語法(as Syntax):
let someValue: any = "hello world";
let strLength: number = (someValue as string).length;
在上面的例子中,我們使用as
關鍵字將someValue
斷言為string
類型,并賦給strLength
變量。這種形式的類型斷言更加常用,也更加推薦使用。
需要注意的是,類型斷言只是在編譯時起作用,在運行時并不會影響實際的對象。因此,類型斷言要謹慎使用,確保進行類型斷言的值與斷言的類型是兼容的,否則可能導致運行時錯誤。
例如,如果對一個數字類型的變量進行字符串類型的斷言,可能會導致類型不匹配的錯誤:
let num: number = 123;
let str: string = num as string; // 錯誤,無法將數字類型斷言為字符串類型
總結一下,類型斷言是一種告訴編譯器某個值的確切類型的方式,在TypeScript中可以使用尖括號語法或as語法來實現。但需要注意的是,類型斷言只在編譯時起作用,并不能改變運行時的實際類型。
17.void和never區別
在TypeScript中,void
和never
是兩個不同的類型,用于表示函數返回值或表達式的特性。
void
類型表示函數沒有返回值,或者說函數返回的值為undefined。常見的使用場景是在函數聲明或函數定義時明確指定函數的返回類型為void
。
function greet(): void {console.log("Hello!");
}
在上述示例中,函數greet()
被標注為返回類型為void
,因此該函數沒有返回值。
- never`類型表示函數永遠不會正常結束或返回結果,或者拋出異常。它通常用在以下情況:
- 函數總是會拋出錯誤或產生無法到達的終點。
- 函數包含無限循環,不會停止執行。
function throwError(message: string): never {throw new Error(message);
}function infiniteLoop(): never {while (true) {// 無限循環}
}
在上述示例中,函數throwError
會拋出一個錯誤,并且永遠不會正常返回。函數infiniteLoop
則是一個無限循環,因此也永遠不會正常返回。
總結一下,void
類型用于表示沒有返回值的函數,而never
類型用于表示永遠不會正常返回的函數或表達式。void
標志著函數結束后會得到undefined,而never
標志著函數永遠不會返回
18.函數如何聲明數據類型
在TypeScript中,可以通過以下幾種方式來聲明函數的數據類型:
- 使用函數類型標注(Function Type Annotation):通過在函數名后面使用冒號(:)來指定函數的參數類型和返回值類型。
function add(x: number, y: number): number {return x + y;
}
在上述示例中,函數add
被標注為接受兩個參數 x
和 y
,它們都是數字類型,并且返回值也是數字類型。
- 使用變量類型推斷(Variable Type Inference):如果沒有顯式指定函數的數據類型,TypeScript 可以根據賦值表達式自動推斷出函數的數據類型。
const multiply = (a: number, b: number) => {return a * b;
};
在上述示例中,函數 multiply
的類型會被自動推斷為接受兩個數字類型的參數,并返回一個數字類型的結果。
- 使用接口(Interface)定義函數類型:可以使用接口來描述函數的形狀,其中包含函數參數和返回值的類型定義。
interface MathOperation {(x: number, y: number): number;
}const subtract: MathOperation = (a, b) => {return a - b;
};
在上述示例中,我們定義了一個接口 MathOperation
,它描述了一個接受兩個數字類型參數并返回一個數字類型的函數。然后,我們將函數 subtract
的類型指定為 MathOperation
,確保它符合該接口的定義。
無論使用哪種方式,明確指定函數的參數類型和返回值類型可以提供更好的類型安全性,并且能夠在編譯時捕獲潛在的類型錯誤。
19.tscongfig.json作用
sconfig.json是一個用于配置TypeScript編譯器的配置文件。該文件用于指定項目中的TypeScript編譯選項,以控制編譯器的行為和輸出結果。
以下是tsconfig.json文件的作用:
- 指定編譯選項:可以在tsconfig.json中配置各種編譯選項,如目標版本(target)、模塊系統(module)、輸出目錄(outDir)、源文件列表(files、include、exclude)等。通過這些選項,可以自定義編譯器的行為,以滿足項目需求。
- 代碼檢查和語言特性配置:TypeScript編譯器可以根據配置文件中的選項對代碼進行類型檢查,并提供語言特性的支持。通過配置文件,可以啟用或禁用某些語言特性,設置嚴格的類型檢查級別,處理特定的JavaScript模式等。
- 引入第三方庫的聲明文件:tsconfig.json可以配置使用引入的第三方庫的聲明文件(type definitions)。通過配置"types"或"typeRoots"選項,可以告訴編譯器在編譯過程中使用相應的聲明文件,以獲得更好的類型檢查和編輯器支持。
- 項目整合和管理:通過使用tsconfig.json,可以將整個項目的配置統一管理。開發者可以根據需要創建多個不同的tsconfig.json文件,從而實現項目的模塊化和擴展。
- IDE集成和開發工具支持:大多數IDE和代碼編輯器都會自動檢測項目中的tsconfig.json文件,并根據其配置提供相關的代碼提示、錯誤檢查、重構等功能。tsconfig.json文件可以增強開發者在IDE中的開發體驗。
總之,tsconfig.json文件是TypeScript項目中的關鍵配置文件,用于控制編譯器的行為、配置項目選項以及提供開發工具和IDE的支持。通過合理配置tsconfig.json,可以更好地管理和組織TypeScript項目。
20.如何檢測null和undefined
在 TypeScript 中,可以使用類型斷言、嚴格模式和條件語句來檢測 null
和 undefined
。
- 類型斷言:使用類型斷言來告訴編譯器變量的實際類型,并明確排除
null
和undefined
。
typescript復制代碼let value: string | null = "Hello";
let length: number = (value as string).length;
- 嚴格模式:通過啟用 TypeScript 的嚴格模式(strictNullChecks),編譯器會強制檢查
null
和undefined
的賦值情況。
// tsconfig.json
{"compilerOptions": {"strictNullChecks": true}
}let value: string | null = "Hello";
let length: number = value.length; // 編譯錯誤,提示可能為null
- 條件語句:使用條件語句判斷變量是否是
null
或undefined
。
let value: string | null = "Hello";if (value !== null && value !== undefined) {let length: number = value.length;
}
- 可選鏈操作符(Optional Chaining):在 TypeScript 3.7+ 版本中,可使用可選鏈操作符
?.
來安全地訪問可能為null
或undefined
的屬性或方法。
let obj: { prop?: string } = {};let length: number | undefined = obj.prop?.length;
這些方法可以幫助您在 TypeScript 中檢測和處理 null
和 undefined
值,以確保代碼的安全性和可靠性。使用合適的方法可以避免潛在的空指針異常和未定義的行為
21.非空斷言
非空斷言(Non-null Assertion)是 TypeScript 中的一種語法,用于告訴編譯器一個表達式不會為 null
或 undefined
。
在 TypeScript 中,當我們使用 !
在一個可能為 null
或 undefined
的表達式后面時,就表示我們斷言該表達式不會為 null
或 undefined
。
下面是使用非空斷言的示例:
let value: string | null = "Hello";
let length: number = value!.length; // 使用非空斷言console.log(length); // 輸出:5
在上述示例中,我們在表達式 value.length
后添加了 !
,即 value!.length
,這表示我們斷言 value
不會為 null
或 undefined
,因此可以安全地訪問其 length
屬性。
需要注意的是,使用非空斷言時要確保自己對代碼的控制,以避免出現潛在的空指針異常。如果在使用非空斷言時誤判了表達式的類型,或者在運行時出現了 null
或 undefined
值,依然可能導致錯誤。
非空斷言應該謹慎使用,并且建議在能夠使用其他類型檢查機制(如嚴格模式、條件語句等)的情況下,盡量避免使用非空斷言來提高代碼的健壯性和安全性。
22.ts對象混入
在 TypeScript 中,對象混入(Object Mixing)是一種將多個對象合并成一個對象的技術。它常用于實現對象的復用和組合。
可以通過以下幾種方式來實現對象混入:
- 手動屬性賦值:使用對象的屬性賦值操作,逐個將多個對象的屬性復制到一個新的目標對象中。
function mixin(target: any, ...sources: any[]): void {for (const source of sources) {for (const key in source) {if (source.hasOwnProperty(key)) {target[key] = source[key];}}}
}const objectA = { foo: 1 };
const objectB = { bar: 2 };const mergedObject = {};
mixin(mergedObject, objectA, objectB);console.log(mergedObject); // 輸出:{ foo: 1, bar: 2 }
Object.assign()
方法:使用Object.assign()
方法將多個源對象的屬性合并到目標對象中。
const objectA = { foo: 1 };
const objectB = { bar: 2 };const mergedObject = Object.assign({}, objectA, objectB);console.log(mergedObject); // 輸出:{ foo: 1, bar: 2 }
- 類繼承和 Mixin 類:使用類繼承和 Mixin 類來實現對象混入,從而實現對類的方法和屬性的復用和組合。
class MyMixin {mixMethod() {console.log("Mixin method");}
}class MyClass extends MyMixin {myMethod() {console.log("My class method");}
}const myObject = new MyClass();
myObject.myMethod(); // 輸出:My class method
myObject.mixMethod(); // 輸出:Mixin method
l以上是幾種常見的對象混入方式。根據實際需求和場景,您可以選擇適合的方式進行對象的屬性和方法復用、組合和擴展
23.type和interface區別
在許多編程語言中,包括Java和TypeScript等,type
和interface
是用于定義自定義數據類型的關鍵字。它們在某些方面有相似之處,但也有一些區別。
以下是type
和interface
之間的區別:
- 語法:
type
使用type
關鍵字來定義,而interface
使用interface
關鍵字來定義。 - 聲明方式:使用
type
可以聲明多種形式的類型,包括基本類型、聯合類型、交叉類型、函數類型等。而interface
主要用于聲明對象類型和類的結構。 - 可擴展性:
interface
支持擴展,可以通過繼承其他接口來添加或修改屬性和方法。而type
不支持直接的擴展機制。 - 其他語法特性:
interface
可以聲明可選屬性、只讀屬性和索引簽名等特性,這些特性對于定義對象類型非常有用。而type
可以使用typeof
操作符獲取類型的元數據,并且可以使用as
關鍵字進行類型斷言。 - 使用場景:
interface
通常用于描述對象的結構,例如定義一個接口來表示用戶、產品或API響應的數據結構。而type
適用于更復雜的類型定義,例如聯合類型、交叉類型和函數類型的組合。
總體而言,interface
更加專注于對象類型的定義和擴展,而type
則更加靈活,可以用于各種類型的定義。選擇使用哪種關鍵字取決于你遇到的具體需求和上下文。在某些情況下,它們可以互換使用,但在其他情況下,則可能更適合使用其中之一。
24.ts變量及變量提升
在TypeScript中,變量聲明與JavaScript類似,并且遵循JavaScript的變量提升機制。
變量聲明包括兩種關鍵字:var
和let
(還有const
用于聲明常量)。
-
var
關鍵字: 使用var
聲明的變量存在變量提升,即它們可以在聲明之前被訪問到。typescript復制代碼console.log(name); // 輸出:undefined var name = "John"; console.log(name); // 輸出:"John"
在上述示例中,雖然變量
name
在聲明之前被訪問,但其值為undefined
。這是由于變量提升,使得變量的聲明在代碼執行之前就會被解析。 -
let
關鍵字: 使用let
聲明的變量也存在變量提升,但在聲明之前訪問會拋出錯誤。typescript復制代碼console.log(age); // 拋出 ReferenceError 錯誤 let age = 25; console.log(age); // 輸出:25
在上述示例中,嘗試在變量
age
聲明之前訪問它會導致ReferenceError
錯誤。與var
不同,使用let
聲明的變量在聲明之前是不可訪問的。
值得注意的是,var
存在一些作用域問題,會存在變量提升并且具有函數作用域(函數內部可訪問),而let
和const
具有塊級作用域(只在所在代碼塊內可訪問),并且不存在變量提升。
盡管TypeScript允許使用var
、let
和const
關鍵字聲明變量,但為了遵循最佳實踐并減少錯誤,推薦使用let
和const
來聲明變量,并根據需要選擇合適的作用域。
25.ts裝飾器
在TypeScript中,裝飾器(Decorators)是一種特殊的聲明,用于修改類、方法、屬性或參數的行為。裝飾器可以附加元數據、修改類的定義、替換類的方法或屬性。
裝飾器使用@
符號緊跟在被修飾的目標之前,并可以接收一些參數。它們可以應用于類、類中的方法、類的屬性以及類方法的參數。
下面是一些常見的裝飾器用法:
-
類裝飾器:
function MyDecorator(target: Function) {// 在此處可以修改類的行為或添加附加元數據 }@MyDecorator class MyClass {// 類定義 }
類裝飾器在類聲明之前對類進行修飾。它接收一個參數,即被修飾的類的構造函數。在裝飾器內部,可以對類的行為進行修改或添加附加元數據。
-
方法裝飾器:
class MyClass {@MyDecoratormyMethod() {// 方法定義} }
方法裝飾器應用于類方法,并可以修改方法的行為、添加附加元數據或替換方法的實現。它接收三個參數:被修飾的類的原型、方法的名稱和方法的屬性描述符。
-
屬性裝飾器:
class MyClass {@MyDecoratormyProperty: string; }
屬性裝飾器應用于類的屬性,并允許修改屬性的行為或添加附加元數據。它接收兩個參數:被修飾的類的原型和屬性的名稱。
-
參數裝飾器:
class MyClass {myMethod(@MyDecorator param: string) {// 方法定義} }
參數裝飾器應用于方法的參數,并可以修改參數的行為或添加附加元數據。它接收三個參數:被修飾的類的原型、方法的名稱和參數的索引。
裝飾器可以應用多個,多個裝飾器按照從上到下的順序進行求值和應用。
26.ts混入
在TypeScript中,混入(Mixin)是一種將多個類的功能組合到一個類中的技術。它允許一個類通過復用其他類的方法和屬性來獲取額外的功能。
使用混入可以避免類之間的繼承層次結構過于復雜或產生重復代碼的情況。它提供了一種更靈活和可復用的方式來組合不同類的功能。
要實現混入,在TypeScript中可以使用以下幾種方式:
-
類繼承混入:
class Animal {eat(): void {console.log("Animal is eating");} }class Flyable {fly(): void {console.log("Flying...");} }// 使用 extends 關鍵字將 Animal 和 Flyable 混入到 Bird 中 class Bird extends Animal implements Flyable {}const bird = new Bird(); bird.eat(); // 輸出:Animal is eating bird.fly(); // 輸出:Flying...
在上面的示例中,
Bird
類通過extends
關鍵字同時繼承了Animal
類,并使用implements
關鍵字實現了Flyable
接口。這樣,Bird
類就具有了Animal
類和Flyable
接口中定義的方法。 -
對象復制混入:
class Animal {eat(): void {console.log("Animal is eating");} }class Flyable {fly(): void {console.log("Flying...");} }function mixin(target: any, ...sources: any[]): void {Object.assign(target, ...sources); }const bird: any = {}; mixin(bird, new Animal(), new Flyable());bird.eat(); // 輸出:Animal is eating bird.fly(); // 輸出:Flying...
在上述示例中,通過定義一個
mixin
函數,在該函數內部使用Object.assign
方法將多個對象的屬性和方法復制到目標對象中。通過調用mixin
函數將Animal
類和Flyable
類的實例復制到bird
對象上,從而實現混入的效果。
無論是類繼承混入還是對象復制混入,混入都可以在一個類中組合多個類或對象,從而獲得它們的功能。這種方式可以讓代碼更加模塊化、可維護,并促進代碼重用。
27.封裝map方法
要封裝 map
方法,你可以使用泛型和函數類型來定義一個通用的 map
函數。下面是一個示例:
function map<T, U>(arr: T[], callback: (value: T, index: number, array: T[]) => U): U[] {const result: U[] = [];for (let i = 0; i < arr.length; i++) {result.push(callback(arr[i], i, arr));}return result;
}// 使用示例
const numbers = [1, 2, 3, 4];
const doubled = map(numbers, (value) => value * 2);
console.log(doubled); // [2, 4, 6, 8]
在上面的示例中,map
函數接受一個數組和一個回調函數作為參數。回調函數會被應用到數組的每個元素上,并返回一個新的數組。map
函數使用泛型 T
和 U
來表示輸入數組的類型和輸出數組的類型。回調函數的類型是 (value: T, index: number, array: T[]) => U
,其中 value
是當前元素的值,index
是當前元素的索引,array
是原始數組。函數體內部使用一個循環遍歷數組,并將回調函數的結果添加到結果數組中,最后返回結果數組。
你可以根據需要修改回調函數的實現,以適應不同的需求。