亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

TypeScript 函數(Function)

本節介紹 TypeScript 的函數,函數是任何應用程序的基本構建部分,通過函數返回一個計算后的值。

TypeScript 的函數聲明中函數類型是極為重要的,函數的參數都需要標注參數類型,這可以幫助編譯器進行正確的類型推導。本節還會著重講解 this 的使用,可以通過編譯選項和 this 參數兩種方法,正確理解 this 的指向。

1. 慕課解釋

在 JavaScript 中,函數是頭等(first-class)對象,因為它們可以像任何其他對象一樣具有屬性和方法。在 JavaScript 中,每個函數都是一個 Function 對象。

TypeScript 又為 JavaScript 函數添加了一些額外的功能,讓我們可以更容易地使用:

  • 函數類型
  • 可選參數
  • 默認參數
  • 剩余參數
  • 函數重載

2. 函數類型

在 TypeScript 中編寫函數,需要給形參和返回值指定類型:

const add = function(x: number, y: number): string {
  return (x + y).toString()
}

代碼解釋:

參數 x 和 y 都是 number 類型,兩個參數相加后將其類型轉換為 string, 所以整個函數的返回值為 string 類型。

上面的代碼只是對 = 等號右側的匿名函數進行了類型定義,等號左側的 add 同樣可以添加類型:

const add: (x: number, y: number) => string = function(x: number, y: number): string {
  return (x + y).toString()
}

可以看到,等號左側的類型定義由兩部分組成:參數類型和返回值類型,通過 => 符號來連接。

這里要注意:函數類型的 => 和 箭頭函數的 => 是不同的含義。

通過箭頭函數改寫一下剛才寫的函數:

const add = (x: number, y: number): string => (x + y).toString()

等號左右兩側書寫完整:

// 只要參數位置及類型不變,變量名稱可以自己定義,比如把兩個參數定位為 a b
const add: (a: number, b: number) => string = (x: number, y: number): string => (x + y).toString()

3. 函數的參數

3.1 參數個數保持一致

TypeScript 中每個函數參數都是必須的。 這不是指不能傳遞 null 或 undefined 作為參數,而是說編譯器會檢查用戶是否為每個參數都傳入了值。簡短地說,傳遞給一個函數的參數個數必須與函數期望的參數個數一致。

const fullName = (firstName: string, lastName: string): string => `${firstName}${lastName}`

let result1 = fullName('Sherlock', 'Holmes')
let result2 = fullName('Sherlock', 'Holmes', 'character') // Error, Expected 2 arguments, but got 3
let result3 = fullName('Sherlock')                        // Error, Expected 2 arguments, but got 1

代碼解釋:

第 1 行,一個需要傳入 2 個字符串類型參數的函數類型定義。

第 4 行,result2 傳入了 3 個參數,與聲明的 2 個參數不符。

第 5 行,result3 只傳入了 1 個參數,同樣與聲明的 2 個參數不符。

3.2 可選參數

在 JavaScript 中每個參數都是可選的,可傳可不傳。沒傳參的時候,它的值就是 undefined。 而在 TypeScript 里我們可以在參數名旁使用 ? 實現可選參數的功能,可選參數必須跟在必須參數后面。

const fullName = (firstName: string, lastName?: string): string => `${firstName}${lastName}`

let result1 = fullName('Sherlock', 'Holmes')
let result2 = fullName('Sherlock', 'Holmes', 'character') // Error, Expected 1-2 arguments, but got 3
let result3 = fullName('Sherlock')                        // OK

代碼解釋:

第 1 行,firstName 是必須參數,lastName 是可選參數。

第 4 行,傳入了 3 個參數,與聲明的 2 個參數不符。

第 5 行,lastName 是可選參數,可以省略。

3.3 默認參數

參數可以取默認值,上面介紹的可選參數必須跟在必須參數后面,而帶默認值的參數不需要放在必須參數的后面,可隨意調整位置

const token = (expired = 60*60, secret: string): void  => {}
// 或
const token1 = (secret: string, expired = 60*60 ): void => {}

代碼解釋:

第 1 行,帶默認值的參數 expired 在參數列表首位。

第 3 行,帶默認值的參數 expired 在參數列表末位。

3.4 剩余參數

有的時候,函數的參數個數是不確定的,可能傳入未知個數,這時沒有關系,有一種方法可以解決這個問題。

通過 rest 參數 (形式為 ...變量名)來獲取函數的剩余參數,這樣就不需要使用 arguments 對象了。

function assert(ok: boolean, ...args: string[]): void {
  if (!ok) {
    throw new Error(args.join(' '));
  }
}

assert(false, '上傳文件過大', '只能上傳jpg格式')

代碼解釋:

第 1 行,第二個參數傳入剩余參數,且均為字符串類型。

第 7 行,調用函數 assert() 時,除了第一個函數傳入一個布爾類型,接下來可以無限傳入多個字符串類型的參數。

TIP:注意 rest 參數 只能是最后一個參數。

3.5 this 參數

JavaScript 里,this 的值在函數被調用的時候才會被指定,但是這個 this 到底指的是什么還是需要花點時間弄清楚。

默認情況下,tsconfig.json 中,編譯選項 compilerOptions 的屬性 noImplicitThisfalse,我們在一個對象中使用的 this 時,它的類型是 any 類型。

let triangle = {
  a: 10,
  b: 15,
  c: 20,
  area: function () {
    return () => {
      // this 為 any 類型
      const p = (this.a + this.b + this.c) / 2
      return Math.sqrt(p * (p - this.a) * (p - this.b) *(p - this.c))
    }
  }
}

const myArea = triangle.area()
console.log(myArea())

代碼解釋:

在實際工作中 any 類型是非常危險的,我們可以添加任意屬性到 any 類型的參數上,比如將 const p = (this.a + this.b + this.c) / 2 這句改為 const p = (this.d + this.d + this.d) / 2 也不會報錯,這很容易造成不必要的問題。

所以我們應該明確 this 的指向,下面介紹兩種方法:

第一種,在 tsconfig.json 中,將編譯選項 compilerOptions 的屬性 noImplicitThis 設置為 true,TypeScript 編譯器就會幫你進行正確的類型推斷:

let triangle = {
  a: 10,
  b: 15,
  c: 20,
  area: function () {
    return () => {
      const p = (this.a + this.b + this.c) / 2
      return Math.sqrt(p * (p - this.a) * (p - this.b) *(p - this.c))
    }
  }
}

const myArea = triangle.area()
console.log(myArea())

代碼解釋:

noImplicitThis 設置為 true 以后,把鼠標放在第 7 行的 this 上,可以看到:

  this: {
    a: number;
    b: number;
    c: number;
    area: () => () => number;
  }

這時,TypeScript 編譯器就能準確的知道了 this 的類型,如果取不存在于 this 屬性中的 d,將會報錯 Property 'd' does not exist on type '{ a: number; b: number; c: number; area: () => () => any; }'

除了這種方法,我們還可以通過 this 參數 這種形式來解決 this 為 any 類型這一問題。提供一個顯式的 this 參數,它出現在參數列表的最前面:

// 語法
function f(this: void) {

}

改造剛才的例子:

實例演示
預覽 復制
復制成功!
interface Triangle {
  a: number;
  b: number;
  c: number;
  area(this: Triangle): () => number;
}

let triangle: Triangle = {
  a: 10,
  b: 15,
  c: 20,
  area: function (this: Triangle) {
    return () => {
      const p = (this.a + this.b + this.c) / 2
      return Math.sqrt(p * (p - this.a) * (p - this.b) *(p - this.c))
    }
  }
}

const myArea = triangle.area()
console.log(myArea())
運行案例 點擊 "運行案例" 可查看在線運行效果

代碼解釋:

我們聲明了一個接口 Triangle,其中的函數類型顯式的傳入了 this 參數,這個參數的類型為 Triangle 類型(第 5 行):

area(this: Triangle): () => number;

此時,在第 14 行,this 指向 Triangle,就可以進行正確的類型判斷,如果取未定義參數,編譯器將直接報錯。

4. 函數重載

函數重載是指函數根據傳入不同的參數,返回不同類型的數據。

它的意義在于讓你清晰的知道傳入不同的參數得到不同的結果,如果傳入的參數不同,但是得到相同類型的數據,那就不需要使用函數重載。

比如面試中??嫉淖址崔D問題,這里就不考慮負數情況了,只是為了演示函數重載:

function reverse(target: string | number) {
  if (typeof target === 'string') {
    return target.split('').reverse().join('')
  }
  if (typeof target === 'number') {
    return +[...target.toString()].reverse().join('')
  }
}

console.log(reverse('imooc'))   // coomi
console.log(reverse(23874800))  // 847832

編譯器并不知道入參是什么類型的,返回值類型也不能確定。這時可以為同一個函數提供多個函數類型定義來進行函數重載。

(通過 --downlevelIteration 編譯選項增加對生成器和迭代器協議的支持)

實例演示
預覽 復制
復制成功!
function reverse(x: string): string
function reverse(x: number): number

function reverse(target: string | number) {
  if (typeof target === 'string') {
    return target.split('').reverse().join('')
  }
  if (typeof target === 'number') {
    return +[...target.toString()].reverse().join('')
  }
}
console.log(reverse('imooc'))   // coomi
console.log(reverse(23874800))  // 847832
運行案例 點擊 "運行案例" 可查看在線運行效果

代碼解釋:

因為這個反轉函數在傳入字符串類型的時候返回字符串類型,傳入數字類型的時候返回數字類型,所以在前兩行進行了兩次函數類型定義。在函數執行時,根據傳入的參數類型不同,進行不同的計算。

為了讓編譯器能夠選擇正確的檢查類型,它會從重載列表的第一個開始匹配。因此,在定義重載時,一定要把最精確的定義放在最前面。

5. 使用函數時的注意事項

  1. 如果一個函數沒有使用 return 語句,則它默認返回 undefined
  2. 調用函數時,傳遞給函數的值被稱為函數的 實參(值傳遞),對應位置的函數參數被稱為 形參。
  3. 在函數執行時, this 關鍵字并不會指向正在運行的函數本身,而是 指向調用函數的對象。
  4. arguments 對象是所有(非箭頭)函數中都可用的 局部變量。你可以使用 arguments 對象在函數中引用函數的參數。

6. 小結

本節介紹了 TypeScript 中函數的一些新增功能,編寫 TypeScript 代碼一定要將類型的概念了解透徹,無論是變量還是函數,都要記得進行類型定義。