初探 TypeScript

sunseekers發表於2018-07-03

前段時間有朋友和我推薦 TypeScript ,他說寫起來特別爽,讓我去試一試,那時候我還在那是啥高深莫測的東西。剛好那段時間忙,一直沒有時間看。最近也很忙,還是抽時間來探一探 TypeScript ;簡單說 ts 主要提供的是 dynamic type check,提供的 interface 介面這個功能在開發專案的時候會很有幫助。TypeScriptJavaScript 的一個超集。他和 JavaScript 有著千絲萬縷的關係。

TypeScript 相關命令

tsc -init     //ts檔案編譯成js檔案
tsc -w    //實時監控檔案編譯,即一遍寫ts程式碼儲存的時候就可以看到js程式碼
複製程式碼

執行了 tsc -init 以後會生成一個 tsconfig.json 配置檔案

初探 TypeScript
敬畏每一行程式碼,敬畏每一分託付

函式

初探 TypeScript
函式是 JavaScript 裡面最基本的單位,我首先從函式入手慢慢的去學習更多的 TypeScript 語法,進而進一步掌握 ts的用法;

需要驗證函式引數型別,最基本的包括,stringnumberstring[],number[],還有元組( = > 進入元組的學習=>基本型別的學習) 和 JavaScript 一樣,TypeScript 函式可以建立有名字的函式和匿名函式

function add(x:number,y:number):number {
  return x + y
}   //有名函式
let myAdd = function(x:number,y:number):number {
  return x + y
}   //匿名函式
複製程式碼

我們只對程式碼右側的匿名函式進行了型別定義,而等號左邊的 myAdd 是通過賦值操作進行型別推斷出來的,書寫完整的函式型別。(型別推斷:如果沒有明確的指定型別,那麼 TypeScript 會依照型別推論(Type Inference)的規則推斷出一個型別。)

let myAdd: (x:number,y:number) => number = 
function(x: number,y:number):number { return x + y}
複製程式碼

函式型別包含兩部分: 引數型別和返回值型別;在 TypeScript 的型別定義中, => 用來表示函式的定義,左邊是輸入型別,需要用括號括起來,右邊是輸出型別,和 ES6 的箭頭函式不一樣

可選引數和預設引數

TypeScript 裡的每一個函式引數都是必須的,傳遞給函式的引數個數必須與函式期望的引數個數一致,否則會報錯。

必填引數

function buildName(firstName:string,lastName:string) {
  return firstName + " " + lastName
}
let result0 = buildName(12, 12);    //提示 12 型別的引數不能賦值給 string
let result1 = buildName('Bob')    //提示應該有兩個引數,但是隻獲得一個
let result2 = buildName('Bob','Adams','"sr')    //提示應該有兩個引數,但是隻獲得三個
let result3 = buildName("Bob", "Adams");    //引數和傳入的引數一樣,不提示
複製程式碼

可選引數

實現引數可選功能,我們需要在引數名旁邊加 ?,但是可選引數必須跟在引數後面

function selectParam(firstName:string,lastName?:string) {
  return firstName + " " + lastName
}
let selectParam1 = selectParam('bob')
let selectParam2 = selectParam('bob','Adam')
let selectParam2 = selectParam('bob','Adam')    //兩個變數無法重新宣告
複製程式碼

預設引數

我們可以為引數提供預設值,如果帶預設值的引數出現在必須引數前面,使用者必須明確的傳入 undefined 值來獲得預設值

function param(firstName:string,lastName = 'Smith') {
  return firstName + ' ' + lastName
}
let param1 = param('bob')
let param2 = param('bob','Adam','kk')   //提示引數應該是1-2個
複製程式碼

剩餘引數

必要引數,預設引數和可選引數都是表示某一個引數,有時候不知道要操作多少個引數,我們可以用 ... 來操作剩餘引數

function restParam (firstName:string,...restOfName:string[]) {
  return firstName + " " + restOfName.join(' ')
}
let employName = restParam ('Joseph',"Samuel","Bob")
//restParam 可以換一種形式
let restParamFun:(fname:string,...rest:string[]) => string = restParam
複製程式碼

this 和 箭頭函式(這裡好像有問題,待修改)

JavaScript 裡面 this 的值在函式被呼叫的時候指定。但 TypeScript 建立時候指定的

interface Card {
  suit: string;
  card: number;
}
interface Deck {
  suits: string[];
  cards: number[];
  createCardPicker(this:Deck):()=>Card;     //this 指向 Deck
}
let deck:Deck = {
  suits:['hearts','spades','clubs'],
  cards:Array(52),
  createCardPicker:function(this:Deck) {
    return () => {
      let pickedCard = Math.floor(Math.random()*52)
      let pickedSuit = Math.floor(pickedCard / 13);
      return { suit: this.suits[pickedSuit],card:pickedCard % 13}
    }
  }
} 
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
複製程式碼

如果是 JavaScript 會報錯,此時 this 指向了 window,但是TypeScript 不會報錯,他指定了 this 會在哪個物件上面呼叫

基本型別

初探 TypeScript

JavaScript 的型別分為兩種:原始資料型別(BooleannumberstringnullundefinedSynmbol)和物件型別,在 TypeScript 中原始型別資料也是使用。為了讓程式有價值,我們需要能夠處理最簡單的資料單元,數字,字串

數字,字串,陣列

let decLiteral:number = 6   //數字型別
let name1:string = 'bob'    //字串型別
let sentence:string = `Hello, my name is ${name1}`    //字串模板
let list0:number[] = [1,2,3,4]    //[]形式定義陣列
let list1:string[]=['12','12','90']
let list2:Array<number> = [1,23,4]    //Array<元素型別>
let list3:Array<string> = ['1','23','4']    //Array<元素型別>
複製程式碼

TypeScript 中陣列型別有多重定義方式,比較靈活

  • 型別 + 方括號 表示法
let fibonacci:number[] = [1,2,3,4]//只能傳number型別的,否則會提示錯誤
複製程式碼
  • 2.陣列泛型 (=> 跳到泛型去學習)
let fibinacci: Array<number> = [1,2,3,4]
複製程式碼
  • 3.用介面表示陣列 (=> 跳到介面去學習)
interface NumberArray {
 [index:number]: number
}
let fibonacci: NumberArray = [1,2,3,4]
複製程式碼

NumberArray 表示:是一個數字陣列,index 是陣列的索引型別,: 後面表示是一個數字組成的陣列(這樣表述好像還有點怪,歡迎指正)

元組 Tuple

元組型別允許表示一個已知元素數量和型別的陣列,各元素的型別不必相同(陣列合並了相同型別的物件,而元組合並了不同型別的物件)

let x:[string,number];
x = ['Hello',10]
複製程式碼

列舉:取值被限定在一定範圍內的場景

比如說一週只有七天

enum Color { Red,Green,Blue}
let c:Color = Color.Green
複製程式碼

any

在程式設計階段還不清楚型別的變數指定一個型別,值可能是動態輸入,但是 Object 型別的變數值允許你給她賦任意的值,不能在他的上面呼叫方法; 使用 any 型別會導致這個函式可以接受任何型別的引數,這樣會丟失一些資訊;如果我們傳入一個數字,我們只知道任何型別的值都有可能被返回

let list:any[] =  ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
let notSure:any = 4
notSure = "maybe a string instead"
notSure = false  
複製程式碼

void 型別與 any 型別相反

他表示沒有任何型別,當有一個函式沒有返回

function warnUser():void {
  console.log("This is my waring message")
}
複製程式碼

undefinednull ,它們的本身的型別用處不是很大: Never 型別表示的那些永遠不存在的值型別

斷言 as 相信我,我知道自己在幹什麼

let someValue:any = "this is a string"
let strLength:number = (<string>someValue).length//“尖括號”語法
let strLength1: number = (someValue as string).length;//一個為as語法
複製程式碼

聯合型別:表示取值可以是多種型別中的一種

let myFavoriteNumber:string|number;// 連線符 |
myFavoriteNumber = 'seven'
myFavoriteNumber = 7
複製程式碼

泛型

初探 TypeScript
在軟體工程中,我們不僅要建立一致定義良好的 API,同時也要考慮可重用性,元件不僅能夠支援當前的資料型別,同時也能支援未來的資料型別,這在建立大型系統時為你提供了十分靈活的功能 用泛型來建立可重用的元件;泛型是一種特殊的變數,只用於表示型別而不是值

泛型函式

function identity<T>(arg:T):T {
  return arg;
}
let output = identity<string>("myString")
複製程式碼

區別:泛型函式和非泛型函式沒有什麼不同,只是有一個型別引數在最前面,像函式宣告一樣

let myIdentity:<T>(arg:T) => T = identity
let myIdentity1:{ <T>(arg:T):T} = identity
複製程式碼

可以使用帶有呼叫簽名的物件字面量來定義泛型函式,我們可以將物件字面量拿出來作為一個介面,將一個泛型引數當做整個介面的一個引數,這樣我們就能清楚的知道使用的具體是哪個泛型型別

泛型介面

interface GenericIdentityFn {
  <T>(arg:T):T
}
function identity<T>(arg:T):T {
  return arg
}
let myIdentity:GenericIdentityFn = identity
複製程式碼

泛型類 (=>類的學習)

泛型類看上去和泛型介面差不多,泛型類使用(<>)括起泛型型別,跟在類名後面

class GenericNumber<T> {
  zeroValue:T,
  add:(x:T,y:T)=>T
}
let myGenericNumber = new GeneriNumber<number>()
複製程式碼

類有兩個部分:靜態部分和例項部分,泛型類指的例項部分,所以靜態屬性不能使用這個泛型型別,定義介面來描述約束條件

泛型約束

interface Lengthwise {
  length:number
}
function loggingIdentity<T extends Lengthwise>(arg:T):T {
  console.log(arg.length)
  return arg
}
複製程式碼

extends 繼承了一個介面進而對泛型的資料結構進行了限制

介面

初探 TypeScript
TypeScript 核心原則之一是對值所具有的結構進行型別檢查,它是對行為的抽象,具體行動需要有類去實現,一般介面首字母大寫。一般來講,一個類只能繼承來自另一個類。有時候不同類之間可以有一些共有的特性,這時候就可以把特性提取成介面,用 inplements 關鍵字來實現,這個特性大大提高了物件導向的靈活性

可選屬性的好處:可能存在的屬性進行定義,捕獲引用了一個不存在的屬性時的錯誤

只讀屬性(readonly)

一些物件屬性只能在物件剛剛建立的時候修改它的值

interface Point {
  readonly x:number;
  readonly y:number;
}
複製程式碼

TypeScript 具有 ReadonlyArray<T> 型別,它與 Array<T> 相似只是把所有的可變方法去掉了,確保陣列建立後再也不能被修改

readonly vs const

如果我們要把他當做一個變數就使用 const ,若為屬性則使用readonly

額外的屬性檢查

1.可以使用型別斷言繞過這些檢查(斷言的兩種形式)

let strLength:number = (<string>someValue).length   //“尖括號”語法
let strLength1: number = (someValue as string).length    //一個為 `as` 語法
複製程式碼

2.使用索引籤,物件賦值給另一個變數,物件字面量會被特殊對待而且會經過 額外屬性檢查,當將它們賦值給變數或作為引數傳遞的時候

let squareOptions = { colour: "red", width: 100 }
let mySquare = createSquare(squareOptions)
複製程式碼

3.新增字串索引簽名

interface SquareConfig {
  color?:string;
  width?:number;
  [propName:string]:any
}
複製程式碼

函式型別

介面能夠描述 JavaScript 中物件擁有的各種各樣的外形,描述了帶有的普通物件之外,介面也可以描述成函式型別;他有一個呼叫簽名,引數列表和返回值型別的函式定義,引數列表裡的每一個引數都需要名字和型別,函式的引數名不需要與介面裡定義的名字相匹配,如果你沒有指定引數型別,TypeScript 的型別系統會推斷出引數型別

interface SearchFunc {
  (source:string,subString:string):boolean;
}
let MySearch:SearchFunc;
MySearch = function(source:string,subString:string) {
  let result = source.search(subString);
  return result > -1
}
複製程式碼

可索引的型別

interface StringArray {
  [index:number]:string
}
//let myArray:StringArray = ["Bob",'Fred']
let myArray:StringArray;
myArray = ["Bob",'Fred']
複製程式碼

類型別的實現介面,使用關鍵字 implements

interface ClockInterface {
  currentTime: Date;
}
class Clock implements ClockInterface {
  currentTime:Date;
  constructor(h:number,m:number){}
}
複製程式碼

繼承介面 關鍵字extends

interface Shape {
  color:string;
}
interface PenStroke {
  penWidth:number;
}
interface Square extends Shape,PenStroke {
  sideLength:number
}
let square = <Square>{}
複製程式碼

extends 是繼承某個類, 繼承之後可以使用父類的方法, 也可以重寫父類的方法;

implements 是實現多個介面, 介面的方法一般為空的, 必須重寫才能使用

初探 TypeScript
我們引用的任何一個類成員的時候都用了 this,他表示我們訪問的是類成員

類( Class ):定義一件事情的抽象特點,包括他的屬性和方法

物件( Object ):類的例項,通過 new 生成

物件導向( OOP )的三大特性:封裝,繼承,多型

封裝( Encapsulation ): 將對資料的操作細節隱藏起來,只暴露對外的介面,外界端不需要(也不可能)知道細節,就能通過對外提供的介面訪問該物件,同時也保證了外界無法任意改變物件內部資料

繼承( Inheritance ):子類繼承父類,子類除了擁有父類的所有特性外,還有一些更具體的特性

多型( Polymorphism):由繼承而產生了相關的不同類,對同一個方法有不同的響應。比如 CatDog 都繼承自 Animal,但是分別實現了自己的 eat 方法。此時針對某一個例項,我們無需瞭解它是 Cat 還是 Dog,就可以直接呼叫 eat 方法,程式會自動判斷出來應該如何執行 eat

存取器( getter & setter ):用以改變屬性的讀取和賦值行為

修飾器( Modifiers ):修飾符是一些關鍵字,用於限定成員或型別的性質

抽象類(Abstract Class):抽象類是提供其他類繼承的基類,抽象類不允許被例項化,抽象類的抽象方法必須在子類中被實現

介面(Interface):不同類之間公有的屬性和方法,可以抽象成一個介面,介面可以被類實現(implements),一個類只能繼承自另一個類,但是可以實現多個介面

class Greeter {
  greeting:string;
  constructor(message:string) {
    this.greeting = message
  }
  greet() {
    return "Hello" + this.greeting
  }
}
let greeter = new Greeter("World")
複製程式碼

new 構造 Greeter 類的一個例項,呼叫之前定義的建構函式,建立一個Greeter 型別的新物件,執行建構函式初始化他

繼承

通過繼承來擴充套件現有的類,基類通常被稱作超類(Animal),派生類常被稱作子類(Dog

class Animal {
  name: string;
  constructor(theName: string) { this.name = theName; }
  move(distanceInMeters:number = 0) {
    console.log(`Animal moved ${distanceInMeters}`)
  }
}
class Dog extends Animal {
  constructor(name: string) { super(name); }
  bark() {
    console.log("woof woof")
  }
  move(distanceInMeters = 5) {
    super.move(distanceInMeters)
  }
}
const dog = new Dog()
dog.bark()
dog.move(10)
複製程式碼

派生類包含一個建構函式,他必須呼叫 super() ,他會執行基類函式,在構造器函式裡訪問 this 的屬性前,一定要呼叫 super() 。這是 TypeScript 強制執行的一條重要規則

共有私有與受保護的修飾符

在所有 TypeScript 裡,成員都預設為 public

當成員被標記成 private 時,他就不能在宣告他的外部訪問

protectedprivate 修飾符行為很類似,但是有一點不同 protected 成員在派生類中仍然可以訪問。

readonly 關鍵字將屬性設定為只讀,只讀屬性必須在宣告或者建構函式裡被初始化

TypeScript 使用的是結構性型別系統,當我們比較兩種不同的型別的時候,如果型別成員是相容的,我們就認為他們型別是相容的

存取器

TypeScript 支援通過 getters/setters 來擷取對物件成員的訪問

let passcode = 'secret passcode'
class Employee {
  private _fullName:string;
  get fullName():string {
    return this._fullName
  }
  set fullName(newName:string){
    if (passcode && passcode == "secret passcode") {
      this._fullName = newName
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}
複製程式碼

靜態屬性

當屬性只存在於類本身上面而不是類例項上,叫做靜態成員識別符號 static

抽象類

作為其他派生類的基類使用,他們一般不會直接被例項化,抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。抽象方法的語法和介面方法相似,都只是定義方法簽名,但不包括方法體。抽象類可包含成員的實現細節,必須包含 abstract 關鍵字標識和訪問修飾符

abstract class Animal {
  abstract makeSound():void
  move():void {
    console.log('roaming the earch...')
  }
}
複製程式碼

把類當做介面使用

類定義會建立兩個東西:類的例項和一個建構函式,類可以建立型別,所以你能夠在允許使用介面的地方使用類

class Point {
  x:number;
  y:number;
}
interface Point3d extends Point {
  z:number
}
let point3d:Point3d = {x:1,y:2,z:3}
複製程式碼

內建物件

JavaScript 中有很多內建物件,它們可以直接在 TypeScript 中當做定義好了的型別

let b:Boolean = new Boolean(1)
let c:Error = new Error('Error occurred')
let d:Date = new Date()
let r:RegExp = /[a-z]/
複製程式碼

DOMBOM 提供的內建物件,在 TypeScript 中會經常用到

暫時就先寫到這裡了,下次繼續,初學者很多不懂得地方,歡迎指正,學習交流

好像每天都在加班,回家總是很晚。促使我學 TypeScript 最主要的原因是對程式碼有著嚴格的要求,將某些將來可能會出現的 bug 扼殺在搖籃裡。在專案開發過程中,我寫了一個公共的方法用來解析後端傳我的資料格式,忽然有一天某個後端給我的資料結構從字串變成了陣列,就那麼一兩個介面的的資料結構變了,大部分的資料結構沒有變。導致頁面報錯,一行一行程式碼排除,最後找到公共方法,花了我好長一段時間。那時候我就在想 java 多好呀,直接定義資料型別。避免了我這樣的情況。後來我知道了 TypeScript 也可以。慢慢的喜歡上他。對程式碼有著嚴格的要求,提前 debug ,最近準備好好學,在忙都要學,可方便了。

在學習 TypeScript 官方文件的時候,我類比 java 的語法學習,我自己感覺語法挺像的。我還老是問我的同事,你們 java 裡面是不是有那個語法 implementsextends , 還請教了她們在 java 它們的區別。我的同事以為我在學 java ,我回她們說類比學前端

TypeScript 資料求推薦,資源共享,看了兩遍官方文件,以後準備結合專案進行實戰。如果你有相關的開發經驗,想像你學習,交流哈哈,需要一個老司機帶我哈哈