1. 程式人生 > 其它 >前端開發系列044-基礎篇之TypeScript語言特性(四)

前端開發系列044-基礎篇之TypeScript語言特性(四)

title: '前端開發系列044-基礎篇之TypeScript語言特性(四)'
tags:
  - javaScript系列
  - TypeScript系列

categories: []
date: 2017-11-23 18:05:13

本文主要對TypeScript中的函式進行展開介紹。主要包括以下內容

❏ 函式的建立和型別
❏ 函式引數情況說明
❏ 泛型函式簡單介紹

一、函式的建立和型別

函式的建立

函式的建立主要有兩種方式:通過函式宣告建立通過函式表示式建立,在形式上函式又可以被劃分文命名函式匿名函式。此外,TypeScript中還提供了箭頭函式支援,在宣告箭頭函式的時候,我們不再使用function關鍵字轉而使用=>標記。

//檔案路徑 ..07-函式深入講解/01-函式的建立.ts

//測試變數宣告提升
console.log(getName);   //函式
console.log(getNameT);  //undefined
console.log(f1);        //undefined

//[001] 函式宣告的方式建立(命名函式)
function getName(name:string):string{
  return "getName函式=>姓名 :" + name;
}

//[002] 函式表示式的方式建立(匿名函式)
var getNameT = function (name:string):string{
  return "getNameT函式=>姓名 :" + name;
}

//[003] 箭頭函式
var f1 = (name:string):string=>{
  return "f1函式=>姓名 :" + name;
}

console.log(getName("文頂頂"));            //getName函式=>姓名 :文頂頂
console.log(getNameT("wendingding.com")); //getNameT函式=>姓名 :wendingding.com
console.log(f1("奧特曼"));                 //f1函式=>姓名 :奧特曼

因為JavaScript語言中變數宣告提升的特性,所以通過函式宣告方式建立的命名函式通過函式表示式方式建立的匿名函式其使用特徵也很不一樣。熟悉JavaScript語言中變數提升特性的開發者應該非常清楚,所謂變數宣告的提升,指的是在真正執行JavaScript程式碼前直譯器會有個預解析的階段,在這個階段中會檢查程式碼中所有的變數宣告並把這些宣告提升到當前作用域的頂部。

需要注意的是,很多人可能並不能夠準確的區分清楚程式碼中哪部分是變數宣告,哪部分不屬於它,這裡進行簡單說明。
//(1)表示宣告一個變數a,並未賦值
var a;   
//(2)表示宣告一個變數b,並把123賦值給變數b
//這行程式碼有兩部分組成,其結構為 宣告 + 賦值
//等價於 var b; b = 123;兩行程式碼
var b = 123;

JavaScript預解析階段在進行變數宣告提升的時候,僅僅會把變數的宣告部分進行提升,而賦值操作留在原地。因為函式其實本質上也是變數,所以同樣適用上面的規則。

程式碼說明:觀察第一份示例程式碼,程式碼中以函式宣告方式建立的命名函式getName,其作為函式(變數)宣告在預解析階段會被整體提升,而匿名函式(賦值給了變數getNameT)因為有賦值操作,所以在預解析階段只會將變數宣告這部分(var getNameT)提升到作用域頂部,賦值操作會被留在原地。

函式的型別

我們知道在TypeScript語言中,可以使用可選的型別宣告來顯示的指定變數的型別,函式其實也算是變數,多以我們可以在宣告函式的時候,顯示的宣告其型別。

我們先看下面的函式示例程式碼

function add(num1:number,num2:number):string{
  return "傳入的引數分別為:"+num1+"和 "+ num2 + "add的結果為:" + (num1 + num2);
}

程式碼中聲明瞭add函式,並指定了該函式需要接受兩個number型別的引數(分別為num1和num2),返回值為字串型別。其實作為特殊的變數,我們也可以給函式也宣告型別,在進行函式型別宣告的時候,其語法結構同變數可選的型別宣告沒什麼兩樣,結構均為:宣告變數鍵字 + 變數(函式)名 :型別

//001 宣告變數
let str:string;
//002 宣告變數並做初始化賦值操作
let sum:number = 123;

//[001]提供函式變數並顯示的宣告函式的型別(引數和返回值等情況)
let f1:(name:string,age:number) => string;
//賦值操作
f1 = function (name:string,age:number):string
{
  return "姓名:" +name + "年齡:" + age;
}
//函式呼叫
str = f1("zs",18);
console.log(str);   //姓名:zs年齡:18

let f1:(name:string,age:number) => string;這行程式碼中的(name:string,age:number) => string用於表示函式的具體型別,我們可以發現函式的型別宣告由三部分組成:形參 + =>標記 + 返回值型別。上面的程式碼中函式的型別宣告和賦值操作是分開處理的,你當然也可以像普通變數那樣把這兩個操作合二為一,下面給出改寫後的程式碼:

//[002] 宣告函式(函式被指定了型別)
let str1:string;
let f2:(name:string,age:number) => string = function (name:string,age:number):string
{
  return "姓名:" +name + "年齡:" + age;
}  
str1 = f2("zs",18);
console.log(str1);    //姓名:zs年齡:18

建議:在使用TypeScript設計函式的時候,不建議像上面示例程式碼這樣來為函式指明型別,因為函式的型別可以從被賦值的函式推斷出來,因此我們給函式新增型別宣告並不是必需的,相反這樣做還會讓程式碼變得冗餘且難以理解和閱讀。

二、函式引數情況說明

可選引數

在使用JavaScript設計函式的時候,如何函式呼叫時傳入的實參和宣告時的形參不一致也能工作,但TypeScript中會對函式引數的型別以及引數的個數進行更嚴格的檢查,我們來看下面的程式碼示例。


//函式宣告
function getInfo(name:string,age:number,isStudent:boolean) : string{

  let result:string;
  result = "姓名: " + name + "年齡: " + age;
  if(isStudent)
  {
    result += " 是否為學生? " + isStudent;
  }
  return result;
}

//函式呼叫
console.log(getInfo("文頂頂",18,true));  //姓名: 文頂頂 年齡: 18 是否為學生? true

//錯誤的演示:error TS2554: Expected 3 arguments, but got 2.
console.log(getInfo("文頂頂",20));

如果函式中某些引數並非必須的,我們希望該函式在呼叫的時候無論是否傳遞某些引數,函式都要能夠繼續工作,這需要用上TypeScript為我們提供的可選引數特性

函式可選引數的具體用法非常簡單,我們只需要在函式形參名稱後面加上一個?字元即可,調整getInfo方法如下

//檔案路徑 ../07-函式深入講解/03-函式的可選引數02.ts

//函式宣告
function getInfo(name:string,age:number,isStudent?:boolean) : string{

  let result:string;
  result = "姓名: " + name + " 年齡: " + age;
  if(isStudent)
  {
    result += " 是否為學生? " + isStudent;
  }
  return result;
}

//函式呼叫
console.log(getInfo("文頂頂",18,true));  //姓名: 文頂頂 年齡: 18 是否為學生? true
console.log(getInfo("文頂頂",20));       //姓名: 文頂頂 年齡: 20
注意:所有的可選引數必須位於必選引數列表的最後。

引數的預設值

當函式中存在可選引數的時候,在函式體中我們必須要對可選引數是否傳遞進行檢測,這種情況我們使用為可選引數設定預設值會更合適。

在上面的程式碼中,因為getInfo函式的isStudent屬性是可選的,所以我們在函式體內的實現程式碼中訪問isStudent前必須先進行檢查,而通過形參名稱 : 引數型別 = 預設值的方式能夠避免這樣做,還能夠大大的提升程式碼的可閱讀性。

//檔案路徑 ../07-函式深入講解/05-函式的可選引數03.ts

//函式宣告
function getInfo(name:string,age:number,isStudent:boolean = false) : string{
  return "姓名: " + name + " 年齡: " + age +  " 是否為學生? " + isStudent;
}

//函式呼叫
console.log(getInfo("文頂頂",18,true));  //姓名: 文頂頂 年齡: 18 是否為學生? true
console.log(getInfo("文頂頂",20));       //姓名: 文頂頂 年齡: 20 是否為學生? false
注意:所有設定預設值的引數必須位於所有必選引數列表的最後。

引數不確定的函式

我們在設計函式的時候,有時該函式能夠接受的引數個數是不確定的,比如說現在需要設計一個計算累加和的函式,該函式能夠接受任意多個number型別的資料。我們可以通過...形參名:引數型別的方式來處理這種情況。

//檔案路徑 ../07-函式深入講解/06-函式的引數不確定.ts
//[001] 在宣告函式的時候,不提供形參和型別宣告
//TypeScript編譯不通過,當呼叫時候報錯:sum函式接收的引數個數為0
function sum() {
    var result;
    for (var i = 0; i < arguments.length; i++) {
        result += arguments[i];
    }
    return result;
}
sum(1, 2, 3);
sum(2, 4, 8);


//[002] 在宣告函式的時候,不提供形參和型別宣告
//TypeScript編譯不通過,當呼叫時候報錯:sum函式接收的引數個數為0
function sum1(...numbers:number[]) {
    var result:number = 0;
    for (var i = 0; i < numbers.length; i++) {
        result += numbers[i];
    }
    return result;
}
sum1(1, 2, 3);      //7
sum1(2, 4, 8, 10);  //24

建議: 使用...形參的語法來處理函式引數不確定情況其編譯為JavaScript程式碼後本質還是遍歷arguments,所以其實這種情況,在設計的時候可以考慮讓函式接收一個數組引數。

三、泛型函式簡單介紹

泛型說明

泛型程式設計是一種程式語言的程式設計風格,它允許開發者使用以後才會定義的型別,並在例項化的時候作為引數指定這些型別。簡而言之,當函式中某些引數或返回值的資料型別不確定時,使用泛型程式設計能夠把資料型別作為一種引數傳遞進來。

型別變數T

我們先看一個簡單的示例。

假設現在需要設計這樣一個函式,它接收一個引數並返回任何傳給它的值,引數的型別不指定。在實現這種設計需求的時候,因為函式引數的型別不指定,所以我們可能首先想到的就是使用any型別,給出下面的程式碼示例。

//檔案路徑 ../08-泛型函式/01-泛型函式簡單介紹.ts

//[001] 示例程式碼1
//說明 該函式接收一個string型別的引數,並返回傳入的資料
//缺點 限定了函式的引數型別以及返回值型別必須是string
function f1(str:string):string{
  return str;
}

//wendingding.com
console.log(f1("wendingding.com"));

//[002] 示例程式碼2
//說明 該函式接收一個任意型別的引數,並返回傳入的資料
function f2(arg:any):any{
  return arg;
}
console.log(f2("字串測試"));   //字串測試
console.log(f2(123));          //123
console.log(f2(true));        //true

示例程式碼002解決了引數可以是任意型別的問題,但簡單使用any型別並不足以表達傳入引數和返回值引數必須型別一致這個要求,簡單說傳入的型別與返回的型別應該是相同的這個資訊點丟失了或者說表現得不夠明確。

因此,我們需要一種方法來明確的表示返回值的型別與傳入引數的型別應該是相同的。下面的程式碼中,我們使用型別變數來設計一個泛型函式,型別變數是一種特殊的變數,只用於表示型別而不是值。

//[003] 示例程式碼3
//說明 該泛型函式使用型別變數T來表示接收引數和返回值的型別
function f3<T>(arg:T):T{
  return arg;
}
console.log(f3<string>("字串"));      //字串
console.log(f3<number>(345));          //345
console.log(f3<boolean>(false));        //false

console.log(f3("字串-型別推導"));      //字串-型別推導
console.log(f3(123));                 //123
console.log(f3(true));                //true

程式碼中函式接收引數的型別以及返回值的型別我們使用T這個型別變數來表示,T表示的具體型別由函式呼叫時具體的實參型別決定。如果實參是字串,那麼T就是string型別,如果實參是布林型別的值,比如true或者是false,那麼T就是boolean型別的。

定義了泛型函式後,有兩種呼叫方式

> ❏  呼叫函式的時候,使用`< >`明確的傳入T的型別。
> ❏  利用型別推導來確定T的型別。

泛型函式使用注意

使用泛型函式的時候,編譯器要求我們在函式體內必須正確的使用這個通用的型別。 換句話說,我們必須把這些引數當做是任意型別的資料來組織程式碼,否則可能會出現編譯錯誤。

//檔案路徑 ../08-泛型函式/02-泛型函式使用注意點.ts

//說明 該泛型函式使用型別變數T來表示接收引數和返回值的型別
function fn<T>(arg:T):T{
  console.log("列印length值 = " + arg.length);
  return arg;
}

//報錯:error TS2339: Property 'length' does not exist on type 'T'.
console.log(fn([1,2,3]));            

上面的程式碼在編譯的時候,編譯器報錯T型別沒有length這個屬性。錯誤的原因在於我們在函式體中使用了arg的.length屬性,但是卻沒有在任何地方指明arg具有這個屬性。

型別變數(T)表示的是任意型別,而呼叫這個函式時傳入實參可能是數字或true,它們並不具有.length屬性。如果我們能夠確定函式引數是陣列型別的,而陣列元素的型別不確定,那麼可以像下面這樣來組織程式碼。

//調整組織程式碼的方式[001]
function f1<T>(arg:T[]):T[]{
  console.log("列印length值 = " + arg.length);
  return arg;
}
console.log(f1([1,2,3]));                    //列印length值 = 3 [1,2,3]
console.log(f1(["str1","str2","demo"]));     //列印length值 = 3 ["str1","str2","demo"]


//調整組織程式碼的方式[002]
function f2<T>(arg:Array<T>):Array<T>{
  console.log("陣列的長度為 = " + arg.length);
  return arg;
}
console.log(f2([2,4,8,16]));                    //列印length值 = 4 [2,4,8,16]

使用泛型函式的時候千萬不能先入為主想當然。

備註:該文章所有的示例程式碼均可以點選在Github託管倉庫獲取