TypeScript高頻面試題及解析

2024年2月6日 22点热度 0人点赞

本文整理了一些TypeScript 的面試題,一起來看看吧!

面試題涉及了 TypeScript 語言的各個方面,包括基本語法、類型系統、函數、類、模塊化、泛型、裝飾器等。在面試中,常見的 TypeScript 面試題主要圍繞以下幾個方面展開:

類型系統:考察對 TypeScript 類型系統的理解,包括基本類型、聯合類型、交叉類型、接口、類型別名、類型推斷、類型守衛等。

函數和類:涉及函數參數類型、返回值類型、箭頭函數、函數重載、類的定義、繼承、訪問修飾符等概念。

泛型:考察在函數、類和接口中如何使用泛型來增加代碼的靈活性和復用性。

模塊化:問題可能涉及 ES6 模塊化的語法、導入導出方式以及模塊解析等內容。

裝飾器:了解對裝飾器的使用,包括類裝飾器、方法裝飾器、屬性裝飾器以及參數裝飾器的定義和應用。

編譯配置:熟悉 tsconfig.json 中的配置選項,包括編譯目標、模塊系統、嚴格模式等。

工程化實踐:了解 TypeScript 在項目中的實際應用,如與 JavaScript 的混用、第三方庫的聲明文件使用、類型聲明等。

下面是上述涵蓋內容的具體面試題~

1. 什麼是TypeScript

TypeScript是一種由微軟開發的開源編程語言,它是JavaScript的超集。TypeScript通過添加靜態類型、類、接口和模塊等功能,使得在大型應用程序中更容易進行維護和擴展。它可以被編譯為純JavaScript,從而能夠在任何支持JavaScript的地方運行。使用TypeScript可以幫助開發人員在編碼過程中避免一些常見的錯誤,並提供更好的代碼編輯功能和工具支持。

2. 類型聲明和類型推斷的區別,並舉例應用

類型聲明是顯式地為變量或函數指定類型,而類型推斷是TypeScript根據賦值語句右側的值自動推斷變量的類型。例如:

// 類型聲明
let x: number;
x = 10;
// 類型推斷
let y = 20; // TypeScript會自動推斷y的類型為number

3. 什麼是接口(interface),它的作用,接口的使用場景。接口和類型別名(Type Alias)的區別

接口是用於描述對象的形狀的結構化類型。它定義了對象應該包含哪些屬性和方法。在TypeScript中,接口可以用來約束對象的結構,以提高代碼的可讀性和維護性。例如:

interface Person {
    name: string;
    age: number;
}
function greet(person: Person) {
    return `Hello, ${person.name}!`;
}

接口和類型別名的區別:

  • 接口定義了一個契約,描述了對象的形狀(屬性和方法),以便在多個地方共享。它可以被類、對象和函數實現。
  • 類型別名給一個類型起了一個新名字,便於在多處使用。它可以用於原始值、聯合類型、交叉類型等。與接口不同,類型別名可以用於原始類型、聯合類型、交叉類型等,而且還可以為任意類型指定名字。

4. 什麼是泛型(generic),如何創建泛型函數和泛型類,實際用途

泛型是一種在定義函數、類或接口時使用類型參數的方式,以增加代碼的靈活性和重用性。在TypeScript中,可以使用來創建泛型。例如:

function identity<T>(arg: T): T {
    return arg;
}
// 調用泛型函數
let output = identity<string>("hello");

5. 枚舉(enum)是什麼,它的優勢,應用案例。枚舉和常量枚舉的區別

枚舉是一種對數字值集合進行命名的方式。它們可以增加代碼的可讀性,並提供一種便捷的方式來使用一組有意義的常量。例如:

enum Color {
    Red,
    Green,
    Blue
}
let selectedColor: Color = Color.Red;

枚舉和常量枚舉的區別:

  • 枚舉可以包含計算得出的值,而常量枚舉則在編譯階段被刪除,並且不能包含計算得出的值,它隻能包含常量成員。
  • 常量枚舉在編譯後會被刪除,而普通枚舉會生成真實的對象。

6. 如何處理可空類型(nullable types)和undefined類型,如何正確處理這些類型以避免潛在錯誤

在TypeScript中,可空類型是指一個變量可以存儲特定類型的值,也可以存儲null或undefined。(通過使用可空類型,開發者可以明確表達一個變量可能包含特定類型的值,也可能不包含值(即為null或undefined)。這有助於提高代碼的可讀性,並使得變量的可能取值范圍更加清晰明了)。

為了聲明一個可空類型,可以使用聯合類型(Union Types),例如 number | null 或 string | undefined。 例如:

let numberOrNull: number | null = 10; 
numberOrNull = null; // 可以賦值為null 
let stringOrUndefined: string | undefined = "Hello"; 
stringOrUndefined = undefined; // 可以賦值為undefined

7. 什麼是聯合類型和交叉類型

聯合類型表示一個值可以是多種類型中的一種,而交叉類型表示一個新類型,它包含了多個類型的特性。

  • 聯合類型示例:
// typescript
let myVar: string | number;
myVar = "Hello"; // 合法
myVar = 123; // 合法

  • 交叉類型示例:
interface A {
  a(): void;
}
interface B {
  b(): void;
}
type C = A & B; // 表示同時具備 A 和 B 的特性

8. 什麼是TypeScript中的聲明文件(Declaration Files)

聲明文件(通常以 .d.ts 擴展名結尾)用於描述已有 JavaScript 代碼庫的類型信息。它們提供了類型定義和元數據,以便在 TypeScript 項目中使用這些庫時獲得智能感知和類型安全。

9. 什麼是命名空間(Namespace)和模塊(Module)

模塊

  • 在一個大型項目中,可以將相關的代碼組織到單獨的文件,並使用模塊來導入和導出這些文件中的功能。
  • 在一個 Node.js 項目中,可以使用 import 和 export 關鍵字來創建模塊,從而更好地組織代碼並管理依賴關系。

命名空間

  • 在面向對象的編程中,命名空間可以用於將具有相似功能或屬性的類、接口等進行分組,以避免全局命名沖突。
  • 這在大型的 JavaScript 或 TypeScript 應用程序中特別有用,可以確保代碼結構清晰,並且不會意外地重復定義相同的名稱。

模塊提供了一種組織代碼的方式,使得我們可以輕松地在多個文件中共享代碼,

命名空間則提供了一種在全局范圍內組織代碼的方式,防止命名沖突。

模塊示例:

// greeter.ts
export function sayHello(name: string) {
  return `Hello, ${name}!`;
}
// app.ts
import { sayHello } from './greeter';
console.log(sayHello('John'));

命名空間示例:

// greeter.ts
namespace Greetings {
  export function sayHello(name: string) {
    return `Hello, ${name}!`;
  }
}
// app.ts
<reference path="greeter.ts" />
console.log(Greetings.sayHello('John'));

在上面的示例中:

  • 使用模塊時,我們可以使用 export 和 import 關鍵字來定義和引入模塊中的函數或變量。
  • 而在命名空間中,我們使用 namespace 來創建命名空間,並且需要在使用之前使用 <reference path="file.ts" /> 來引入命名空間。

10. 什麼是類型斷言(Type Assertion)

類型斷言允許程序員手動指定一個值的類型。這在需要明確告訴編譯器某個值的類型時非常有用。

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

11. TypeScript中的可選參數和默認參數是什麼

  • 可選參數允許函數中的某些參數不傳值,在參數後面加上問號?表示可選。
  • 默認參數允許在聲明函數時為參數指定默認值,這樣如果調用時未提供參數值,則會使用默認值。

可選參數示例:

function greet(name: string, greeting?: string) {
  if (greeting) {
    return `${greeting}, ${name}!`;
  } else {
    return `Hello, ${name}!`;
  }
}

默認參數示例:

function greet(name: string, greeting: string = "Hello") {
  return `${greeting}, ${name}!`;
}

12. 類型守衛(Type Guards)是什麼

類型守衛是一種用於在運行時檢查類型的技術,它允許開發人員在特定的作用域內縮小變量的范圍,以確保正確推斷類型。

function isString(test: any): test is string {
  return typeof test === "string";
}
if (isString(input)) {
  // input 在此代碼塊中被收窄為 string 類型
}

13. 索引類型(Index Types)是什麼,好處有什麼

索引類型允許我們在 TypeScript 中創建具有動態屬性名稱的對象,並且能夠根據已知的鍵來獲取相應的屬性類型。 好處:

1.動態屬性訪問

在處理動態屬性名的對象時,可以使用索引類型來實現類型安全的屬性訪問。例如,當從服務器返回的 JSON 數據中提取屬性時,可以利用索引類型來確保屬性名存在並獲取其對應的類型。

2.代碼重用

當需要創建通用函數來操作對象屬性時,索引類型可以幫助我們實現更加通用和靈活的代碼。例如,一個通用的函數可能需要根據傳入的屬性名稱獲取屬性值,並進行特定的處理。

interface ServerData {
  id: number;
  name: string;
  age: number;
  // 可能還有其他動態屬性
}
function getPropertyValue(obj: ServerData, key: keyof ServerData): void {
  console.log(obj\[key]); // 確保 obj\[key] 的類型是正確的 // 這裡可以直接使用索引類型來獲取屬性值
}

3.動態擴展對象

當需要處理來自外部來源(比如 API 響應或數據庫查詢)的動態數據時,索引類型可以讓我們輕松地處理這種情況,而不必為每個可能的屬性手動定義類型。

interface DynamicObject {
  [key: string]: number | string; // 允許任意屬性名,但屬性值必須為 number 或 string 類型
}
function processDynamicData(data: DynamicObject): void {
  for (let key in data) {
    console.log(key   ": "   data\[key]); // 對任意屬性進行處理
  }
}

4.類型安全性

索引類型可以增強代碼的類型安全性,因為它們可以捕獲可能的屬性名拼寫錯誤或鍵不存在的情況。

5.映射類型

TypeScript 還提供了映射類型(Mapped Types)的概念,它們利用索引類型可以根據現有類型自動生成新類型。這在創建新類型時非常有用,特別是當需要在現有類型的基礎上添加或修改屬性時。

14. const和readonly的區別

當在TypeScript中使用const和readonly時,它們的行為有一些顯著的區別:

  • const:
    • const用於聲明常量值。一旦被賦值後,其值將不能被重新賦值或修改。
    • 常量必須在聲明時就被賦值,並且該值不可改變。
    • 常量通常用於存儲不會發生變化的值,例如數學常數或固定的配置值。
const PI = 3.14;
PI = 3.14159; // Error: 無法重新分配常量

  • readonly:
    • readonly關鍵字用於標記類的屬性,表明該屬性隻能在類的構造函數或聲明時被賦值,並且不能再次被修改。
    • readonly屬性可以在聲明時或構造函數中被賦值,但之後不能再被修改。
    • readonly屬性通常用於表示對象的某些屬性是隻讀的,防止外部代碼修改這些屬性的值。
class Person {
    readonly name: string;
    constructor(name: string) {
        this.name = name; // 可以在構造函數中賦值
    }
}
let person = new Person("Alice");
person.name = "Bob"; // Error: 無法分配到"name",因為它是隻讀屬性

總結來說,const主要用於聲明常量值,而readonly則用於標記類的屬性使其隻讀。

15. TypeScript 中 any 類型的作用是什麼,濫用會有什麼後果

在TypeScript中,any類型的作用是允許我們在編寫代碼時不指定具體的類型,從而可以接受任何類型的值。使用any類型相當於放棄了對該值的靜態類型檢查,使得代碼在編譯階段不會對這些值進行類型檢查。

主要情況下,any類型的使用包括以下幾點:

  • 當我們不確定一個變量或表達式的具體類型時,可以使用any類型來暫時繞過類型檢查。
  • 在需要與動態類型的JavaScript代碼交互時,可以使用any類型來處理這些動態類型的值。
  • 有時候某些操作難以明確地定義其類型,或者需要較復雜的類型推導時,也可以使用any類型。

濫用的後果:

盡管any類型提供了靈活性,但由於它會放棄TypeScript的靜態類型檢查,因此濫用any類型可能會降低代碼的健壯性和可維護性。當濫用any類型時,可能會導致以下後果:

1.代碼可讀性下降:

let data: any;
// 代碼中的使用方式
data.someUnknownMethod(); // 在編譯階段不會報錯,但實際上可能是一個錯誤

2.潛在的運行時錯誤:

let myVariable: any = 123;
myVariable.toUpperCase(); // 在編譯階段不會報錯,但在運行時會引發錯誤

3.類型安全受損:

function add(x: any, y: any): any {
    return x   y; // 編譯器無法推斷返回值的具體類型
}

濫用any類型會導致代碼失去了TypeScript強大的類型檢查功能,帶來了如下問題:

  • 可能引入未知的運行時行為和錯誤。
  • 降低了代碼的可維護性和可讀性,因為難以理解某些變量或參數的具體類型。

因此,在實際開發中,應盡量避免過度使用any類型。可以通過合適的類型聲明、接口定義和聯合類型等方式,提高代碼的健壯性和可維護性。

16. TypeScript中的this有什麼需要註意的

在TypeScript中,與JavaScript相比,this的行為基本上是一致的。然而,TypeScript提供了類型註解和類型檢查,可以幫助開發者更容易地理解和處理this關鍵字的使用。

在noImplicitThis為true 的情況下,必須聲明 this 的類型,才能在函數或者對象中使用this。

Typescript中箭頭函數的 this 和 ES6 中箭頭函數中的 this 是一致的。

在TypeScript中,當將noImplicitThis設置為true時,意味著在函數或對象中使用this時,必須顯式聲明this的類型。這一設置可以幫助開發者更明確地指定this的類型,以避免因為隱式的this引用而導致的潛在問題。

具體來說,如果將noImplicitThis設置為true,則在下列情況下必須顯式聲明this的類型:

  • 在函數內部使用this時,需要使用箭頭函數或顯示綁定this。
  • 在某些類方法或對象方法中,需要明確定義this的類型。

示例代碼如下所示:

class MyClass {
  private value: number = 42;
  public myMethod(this: MyClass) {
    console.log(this.value);
  }
  public myMethod2 = () => {
    console.log(this.value);
  }
}
let obj = new MyClass();
obj.myMethod(); // 此處必須傳入合適的 this 類型

通過將noImplicitThis設置為true,TypeScript要求我們在使用this時明確指定其類型,從而在編譯階段進行更嚴格的類型檢查,幫助避免一些可能出現的錯誤和不確定性。

註:noImplicitThis是TypeScript編譯器的一個配置選項,用於控制在函數或對象方法中使用this時的嚴格性。當將noImplicitThis設置為true時,意味著必須顯式聲明this的類型,否則會觸發編譯錯誤。

17. TypeScript數據類型

在TypeScript中,常見的數據類型包括以下幾種:

  • 基本類型
    • number: 表示數字,包括整數和浮點數。
    • string: 表示文本字符串。
    • boolean: 表示佈爾值,即true或false。
    • null、undefined: 分別表示null和undefined。
    • symbol: 表示唯一的、不可變的值。
  • 復合類型
    • array: 表示數組,可以使用number[]或Array<number>來聲明其中元素的類型。
    • tuple: 表示元組,用於表示固定數量和類型的數組。
    • enum: 表示枚舉類型,用於定義具名常量集合。
  • 對象類型
    • object: 表示非原始類型,即除number、string、boolean、symbol、null或undefined之外的類型。
    • interface: 用於描述對象的結構,並且可以重復使用。
  • 函數類型
    • function: 表示函數類型。
    • void: 表示函數沒有返回值。
    • any: 表示任意類型。
  • 高級類型
    • union types: 表示一個值可以是幾種類型之一。
    • intersection types: 表示一個值同時擁有多種類型的特性。

18. interface可以給Function/Array/Class(Indexable)做聲明嗎

在TypeScript中,interface可以用來聲明函數、數組和類(具有索引簽名的類)。下面是一些示例代碼:

1. Interface 聲明函數

class MyClass {
  private value: number = 42;
  public myMethod(this: MyClass) {
    console.log(this.value);
  }
  public myMethod2 = () => {
    console.log(this.value);
  }
}
let obj = new MyClass();
obj.myMethod(); // 此處必須傳入合適的 this 類型

在上述示例中,MyFunc接口描述了一個函數類型,該函數接受兩個參數並返回一個數字。

2. Interface 聲明數組

interface StringArray {
  [index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Alice"];

上面的示例中,StringArray接口描述了一個具有數字索引簽名的字符串數組。意味著我們可以通過數字索引來訪問數組元素。

3. Interface 聲明類(Indexable)

interface StringDictionary {
  [index: string]: string;
}
let myDict: StringDictionary = {
  "name": "John",
  "age": "30"
};

在這個例子中,StringDictionary接口用於描述具有字符串索引簽名的類或對象。這使得我們可以像操作字典一樣使用對象的屬性。

綜上:TypeScript中的interface可以被用來聲明函數、數組和具有索引簽名的類,從而幫助我們定義和限定這些數據結構的形式和行為。

19. TypeScript中的協變、逆變、雙變和抗變是什麼

在TypeScript中,協變(Covariance)、逆變(Contravariance)、雙變(Bivariance)和抗變(Invariance 是與類型相關的概念,涉及到參數類型的子類型關系。下面對這些概念進行解釋,並提供示例代碼。

協變(Covariance)

  • 區別:協變意味著子類型可以賦值給父類型。
  • 應用場景:數組類型是協變的,因此可以將子類型的數組賦值給父類型的數組。

協變表示類型T的子類型可以賦值給類型U,當且僅當T是U的子類型。在TypeScript中,數組是協變的,這意味著可以將子類型的數組賦值給父類型的數組。

let subtypes: string[] = ["hello", "world"];
let supertype: Object[] = subtypes; // 數組是協變的,這是合法的

逆變(Contravariance)

  • 區別:逆變意味著超類型可以賦值給子類型。
  • 應用場景:函數參數類型是逆變的,因此可以將超類型的函數賦值給子類型的函數。

逆變表示類型T的超類型可以賦值給類型U,當且僅當T是U的子類型。在TypeScript中,函數參數是逆變的,這意味著可以將超類型的函數賦值給子類型的函數。

type Logger<T> = (arg: T) => void;
let logNumber: Logger<number> = (x: number) => console.log(x);
let logAny: Logger<any> = logNumber; // 函數參數是逆變的,這是合法的

雙變(Bivariance)

  • 區別:雙變允許參數類型既是協變又是逆變的。
  • 應用場景:對象類型是雙變的,這意味著可以將子類型的對象賦值給父類型的對象,同時也可以將超類型的對象賦值給子類型的對象。

雙變允許參數類型既是協變又是逆變的。在TypeScript中,普通對象類型是雙變的,這意味著可以將子類型的對象賦值給父類型的對象,並且可以將超類型的對象賦值給子類型的對象。

interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}
let animal: Animal = { name: "Animal" };
let dog: Dog = { name: "Dog", breed: "Labrador" };
animal = dog; // 對象類型是雙變的,這是合法的
dog = animal; // 對象類型是雙變的,這也是合法的

抗變(Invariance)

  • 區別:抗變表示不允許類型之間的任何賦值關系。
  • 應用場景:通常情況下,基本類型和類類型是抗變的。

抗變表示不允許類型T和U之間的任何賦值關系,即T既不是U的子類型,也不是U的超類型。在TypeScript中,一般情況下,基本類型和類類型是抗變的。

let x: string = "hello";
let y: string = x; // 這是合法的
let a: Animal = { name: "Animal" };
let b: Animal = a; // 這也是合法的

20. TypeScript中的靜態類型和動態類型有什麼區別

  • 靜態類型是在 編譯期間 進行類型檢查,可以在編輯器或 IDE 中發現大部分類型錯誤。
  • 動態類型是在 運行時 才確定變量的類型,通常與動態語言相關聯。

靜態類型(Static Typing)

  • 定義:靜態類型是指在編譯階段進行類型檢查的類型系統,通過類型註解或推斷來確定變量、參數和返回值的類型。
  • 特點:靜態類型能夠在編碼階段就發現大部分類型錯誤,提供了更好的代碼健壯性和可維護性。
  • 優勢:可以在編輯器或 IDE 中實現代碼提示、自動補全和類型檢查,幫助開發者減少錯誤並提高代碼質量。

動態類型(Dynamic Typing)

  • 定義:動態類型是指在運行時才確定變量的類型,通常與動態語言相關聯,允許同一個變量在不同時間引用不同類型的值。
  • 特點:動態類型使得變量的類型靈活多變,在運行時可以根據上下文或條件動態地改變變量的類型。
  • 優勢:動態類型可以帶來更大的靈活性,適用於一些需要頻繁變化類型的場景。

區別總結

  • 時機差異:靜態類型在編譯期間進行類型檢查,而動態類型是在運行時才確定變量的類型。
  • 代碼穩定性:靜態類型有助於在編碼階段發現大部分類型錯誤,提高代碼穩定性;動態類型對類型的要求較為靈活,但可能增加了代碼的不確定性。
  • 使用場景:靜態類型適合於大型項目和團隊,能夠提供更強的類型安全性;動態類型適用於快速原型開發和靈活多變的場景,能夠更快地迭代和測試代碼。

21. 介紹TypeScript中的可選屬性、隻讀屬性和類型斷言

  • 可選屬性 使用 ? 來標記一個屬性可以存在,也可以不存在。
  • 隻讀屬性 使用 readonly 關鍵字來標記一個屬性是隻讀的。
  • 類型斷言 允許將一個實體強制指定為特定的類型,使用 <Type> 或 value as Type。

代碼示例:

// 可選屬性
interface Person {
  name: string;
  age?: number; // 可選屬性
}
// 隻讀屬性
interface Point {
  readonly x: number;
  readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // Error: 隻讀屬性無法重新賦值
// 類型斷言
let someValue: any = "hello";
let strLength: number = (someValue as string).length;

22. TypeScript 中的模塊化是如何工作的,舉例說明

答案:

  • TypeScript 中使用 ES6 模塊系統,可以使用 import 和 export 關鍵字來導入和導出模塊。
  • 可以通過 export default 導出默認模塊,在導入時可以使用 import moduleName from 'modulePath'。

代碼示例:

// math.ts
export function sum(a: number, b: number): number {
  return a   b;
}
export function subtract(a: number, b: number): number {
  return a - b;
}
// app.ts
import { sum, subtract } from './math';
console.log(sum(3, 5)); // 輸出 8


結尾

本文整理了一些ts的常見面試問題,希望對你的理解有所幫助。

作者:uu_Q
鏈接:
https://juejin.cn/post/7321542773076082699