TS的函數與變量聲明

本文目錄:

  • 1.函數定義的兩種方式
  • 2.函數類型
  • 3.可選參數和默認參數
  • 4.剩餘參數
  • 5.函數重載
  • 6.變量聲明

函數的幾個基本概念:

  • 一個函數有輸入和輸出,要在 TypeScript 中對其進行約束,需要給函數的參數和返回值都設置一個類型
  • 函數有個特點:函數內部可以使用函數體外部的變量
  • TypeScript 的函數類型定義中,=> 用來表示函數的定義,左邊是輸入類型,需要用括號括起來,右邊是輸出類型。 在 ES6 中,=> 叫做箭頭函數
  • 後面我們會用接口來定義函數的形狀,避免我們在函數的定義那裏寫的代碼過於臃腫,讓代碼結構更清晰

1.函數定義的兩種方式

// 函數聲明
function add(x, y) {
   return x + y;
}
// 函數表達式
let add1 = function add1(x, y) {
     return x + y;
};

上面的代碼如果我們進行如下的調用

console.log(add(1.'2'))  //12
console.log(add(2,true))  //3

JS中存在隱式轉換,當字符串和數字相加的時候,數字會被轉換爲字符串,當數字和boolean進行相加的時候,boolean會被轉換爲數字(false轉爲0,true轉爲1),所以上面代碼的輸出結果並不是我們想要的,而這種bug如果出現在實際項目中,往往是難以定位的。

我們之前說過,任何的js代碼都可以不改代碼的情況下,直接將後綴名改爲ts,可以正常的運行; 是因爲ts的類型推斷,會自動的根據我們傳入的變量和返回的值進行判斷

// 我們使用ts的形式來定義兩個函數
function add(x: number, y: number): number {
    return x + y;
}
let add2 = function(x: number, y: number): number {
    return x + y;
};

但是我們這裏的add2是一個變量,我們沒有給它指定類型,它是由等號右邊的值 由編譯器自動推斷出來add2是一個函數的,我們要自定義一個函數類型; 涉及到參數和返回值

2.函數類型
在 TypeScript 的類型定義中,=> 用來表示函數的定義,左邊是輸入類型,需要用括號括起來,右邊是輸出類型。 在 ES6 中,=> 叫做箭頭函數,但是在TS中,=>表示函數定義

let add2: (x: number, y: number) => number = function(x: number, y: number): number {
        return x + y;
};

上面就是函數類型, 類似於之前學過的聯合類型,枚舉類型,類型別名等自定義類型。當然了=>左邊的參數名稱x,y都是可以隨意更改的,在實際工作中,我們不會使用上面的方式去定義函數類型,因爲這樣寫的代碼太臃腫了,我們會用接口的形式,這部分知識後面會講。

Interface Myfn {
    (xll: number,yll: number): number
}
let add3:MyFn = add  //add就是本文上面的代碼已經定義好的函數

3.可選參數和默認參數

  • JavaScript裏,每個參數都是可選的,可傳可不傳。 沒傳參的時候,它的值就是undefined
  • TypeScript裏參數多了或者少了都是不被允許的;但是我們可以在參數名旁使用 ?實現可選參數的功能
  • 可選參數必須接在必需參數後面,也就是可選參數後面不允許再出現必需參數了
  • 在 ES6 中,我們允許給函數的參數添加默認值,TypeScript會將添加了默認值的參數識別爲可選參數
function fullName(firstName: string, lastName?: string) {
  console.log(lastName);
  return firstName + ' ' + lastName;
}

如果不加?, 這個函數我們就只能傳遞2個參數,多了少了編譯都會失敗;但是在js裏面如果不傳,返回值就是undefined
如果加上了?, 那這個函數第一個參數還是必傳,第二個參數可傳可不傳

console.log(fullName('123', '456'));
console.log(fullName('123'));

可選參數必須接在必選參數後面; 下面這樣定義函數就會編譯報錯

function buildName2(firstName?: string, lastName: string) {
    return firstName + ' ' + lastName;
}

接下來我們來看一下默認參數

function buildName2(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat1 = buildName2('Tom', 'Cat');
let tom1 = buildName2('Tom');

第二次函數調用只傳入了一個參數,因爲我們ts會將添加了默認值的參數設置爲可選參數, 即使不傳遞也可以,就是用的默認值Cat

4.剩餘參數

  • 必要參數,默認參數和可選參數有個共同點:它們表示某一個參數
  • 想同時操作多個參數,或者你並不知道會有多少參數傳遞進來, 在js裏面使用arguments來訪問所有傳入的參數
  • 在TypeScript`裏,你可以把所有參數收集到一個變量裏; 使用...擴展運算符
function getBalls(x: string, ...restOfName: string[]) {
    console.log(restOfName);
    return x + ' ' + restOfName.join(' ');
}
let myBalls = getBalls('basketball', 'football', 'tennis', 'baseball');
console.log(myBalls);

上面的代碼中 restOfName實際上是一個數組。所以我們可以用數組的類型來定義它
restOfName剩餘參數會被當做個數不限的可選參數。 可以一個都沒有,同樣也可以有任意個。 編譯器創建參數數組,名字是你在省略號( ...)後面給定的名字
並且要注意 rest 參數只能是最後一個參數

5.函數重載

  • 同名函數的參數的個數、類型或者順序必須不同(其中有一個不一樣就滿足條件了) 叫函數的重載
  • 重載允許一個函數接受不同數量或類型的參數時,作出不同的處理。 爲同一個函數提供多個函數類型定義來進行函數重載
  • 在定義重載的時候,一定要把最精確的定義放在最前面。因爲查找重載列表,會從第一個重載定義開始使用,如果匹配成功的話就使用,否則就繼續向下查下;最後函數實現時,需要使用 |操作符或者?操作符,把所有可能的輸入類型全部包含進去(不確實的話可以寫any)
  • 函數重載的意義在於能夠讓你知道傳入不同的類型參數得到不同的類型結果,如果傳入的參數不同,但是得到的結果(類型)卻相同,那麼這裏就不需要使用函數重載

現在有這樣一個需求: 我們有一個add函數,它可以接收string類型的參數進行拼接,也可以接收number類型的參數進行相加
下面是按照之前我們所學知識寫的代碼

function add(arg1: string | number, arg2: string | number): number | string{
     if (typeof arg1 === 'string' && typeof arg2 === 'string') {
       return arg1 + arg2;
     } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
       return arg1 + arg2;
     }
}
let a1 = add(1, 2);
let a2 = add('a', 'b');

上面的代碼在調用函數的時候,定義的變量a1和a2沒有去定義類型,只是用了根據等號右邊的值去賦值給左邊的變量,事實上,a1和a2也不能去指定爲string或者number,因爲add函數的返回值就是一個聯合類型number | string,所以是實際工作中如果發生了這種事情,會導致a1和a2使用變得非常混亂,接下來我們使用函數重載的形式對代碼進行優化
先進行函數的重載,定義函數,將參數和返回值的類型定死

function add(arg1: string, arg2: string): string;
function add(arg1: number, arg2: number): number;

接下來進行函數的實現

function add(arg1: string | number, arg2: string | number): number | string{
     if (typeof arg1 === 'string' && typeof arg2 === 'string') {
       return arg1 + arg2;
     } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
       return arg1 + arg2;
     }
}
let a1 = add(1, 2);
let a2 = add('a', 'b');

這時候就可以明確a1和a2的類型了,a1是number類型,a2是string類型
注意,function add(arg1: string | number, arg2: string | number): number | string並不是重載列表的一部分,因此這裏只有兩個重載:一個是接收數字,另一個接收字符串。 以其它參數調用會產生錯誤

6.變量聲明

  1. 在typescript裏面推薦大家使用let關鍵字來代替大家所熟悉的JavaScript關鍵字varlet關鍵字是JavaScript的一個新概念,TypeScript實現了它
  2. 我們先使用typescirpt的語法將var聲明變量的幾個問題和使用let聲明做一個對比

變量聲明提升 (作用域規則)

function f(flag: boolean):any {
    if (flag) {
        var x:number = 10;
    }
    return x;
}
console.log(f(true));
console.log(f(false));

大家會發現,我們將變量x定義在if語句塊內部,但是卻能在外部訪問它;這就是var的函數作用域;並且flag爲false的時候,也會有打印(undefined),而不是說not defined; 這就是var的變量聲明提升
如果換爲let, x就只能在if塊裏面使用,當flag爲false時,就會顯示x沒有定義,通過看編譯後的源代碼我們也能夠發現ts轉爲js,tsc將變量名直接都改變了,當然就找不到x這個變量的定義了
當用let聲明一個變量,它使用的是詞法作用域或塊作用域;塊作用域變量在包含它們的塊或for循環之外是不能訪問的; 並且沒有聲明提升

多次重複聲明同一個變量不會報錯

var a:number = 1;
var a:number = 2;
console.log(a)  

javascript裏面不會提升我們是否多次聲明瞭同一個變量,遇到這種情況,js會對忽略後續的聲明;但是會執行它的重新賦值操作
如果換位let, 在編譯階段就不會通過;會告訴我們 無法重新聲明塊範圍變量“a”

for循環作用域問題

for (var i:number = 0; i < 5; i++) {
    setTimeout(function f(): void {
        console.log(i);
    }, 1000);
}

最後的結果不是我們希望的 01234,而是5個5,這是因爲我們傳給setTimeout的每一個函數表達式實際上都引用了相同作用域裏(var聲明的變量只有函數作用域和全局作用域,)的同一個i; 在循環結束後,i的值爲5。 所以當函數被調用的時候,它會打印出5個5

我們可以利用IIFE立即自執行函數表達式,創造多個函數作用域出來,爲獲取到的變量創建了一個新的變量環境

for (var i:number = 0; i < 5; i++) {
   (function(i) {
        setTimeout(function(): void {
            console.log(i);
        }, 100 * i);
   })(i);
      }

當let聲明出現在循環體裏時擁有完全不同的行爲。 不僅是在循環裏引入了一個新的變量環境,而是針對 每次迭代都會創建這樣一個新作用域。 這就是我們在使用立即執行的函數表達式時做的事

      for (let i: number = 0; i < 5; i++) {
        setTimeout(function f(): void {
          console.log(i);
        }, 100 * i);
      }

const聲明是聲明變量的另一種方式

與let聲明相似,但是就像它的名字所表達的,它們被賦值後不能再改變。 換句話說,它們擁有與 let相同的作用域規則,但是不能對它們重新賦值

let l1: number = 9;
l1 = 10;
const c2: number = 9;
c2 = 10;

但是對於複雜數據類型有的點特殊

const c2: any = {
    name: 'jack'
};

下面直接改是不行的,因爲相當於給變量b重新開闢了一個內存空間,地址發生了改變,constant類型的值不能更改

c2 = {
   name: 'Lucy'
};

但是下面的代碼是可以正常執行的,因爲改變的是複雜數據類型的值,它們都存儲在堆裏面,只是一個引用,它們的值就是地址還是保存在棧裏面,沒有更改

c2.name = 'Lucy';

選擇 let還是const

  1. 所有變量除了你計劃去修改的都應該使用const。使用 const也可以讓我們更容易的推測數據的流動
  2. 基本原則就是如果一個變量不需要對它寫入,那麼其它使用這些代碼的人也不能夠寫入它們,並且要思考爲什麼會需要對這些變量重新賦值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章