從 JavaScript 到 TypeScript

本文首發在個人我的博客:muyunyun.cn/posts/66a54…
文中的案例代碼已經上傳到 TypeScriptgit

TypeScript 並非一個徹底新的語言, 它是 JavaScript 的超集,爲 JavaScript 的生態增長了類型機制,並最終將代碼編譯爲純粹的 JavaScript 代碼。程序員

TypeScript 簡介

TypeScript 由 Microsoft(算上 Angular 2 的話加上 Google)開發和維護的一種開源編程語言。 它支持 JavaScript 的全部語法和語義,同時經過做爲 ECMAScript 的超集來提供一些額外的功能,如類型檢測和更豐富的語法。下圖顯示了 TypeScript 與 ES5,ES2015,ES2016 之間的關係。github

使用 TypeScript 的緣由

JavaScript 是一門弱類型語言,變量的數據類型具備動態性,只有執行時才能肯定變量的類型,這種後知後覺的認錯方法會讓開發者成爲調試大師,但無益於編程能力的提高,還會下降開發效率。TypeScript 的類型機制能夠有效杜絕由變量類型引發的誤用問題,並且開發者能夠控制對類型的監控程度,是嚴格限制變量類型仍是寬鬆限制變量類型,都取決於開發者的開發需求。添加類型機制以後,反作用主要有兩個:增大了開發人員的學習曲線,增長了設定類型的開發時間。整體而言,這些付出相對於代碼的健壯性和可維護性,都是值得的。typescript

此外,類型註釋是 TypeScript 的內置功能之一,容許文本編輯器和 IDE 能夠對咱們的代碼執行更好的靜態分析。 這意味着咱們能夠經過自動編譯工具的幫助,在編寫代碼時減小錯誤,從而提升咱們的生產力。編程

對 TypeScript 的簡介到此,接下來對其特有的知識點進行簡單歸納總結,(網上不少教程實際上把 ES6, ES7 的知識點也算進 ts 的知識點了,固然這沒錯~)數組

數據類型

String 類型

一個保存字符串的文本,類型聲明爲 string。能夠發現類型聲明可大寫也可小寫,後文同理。瀏覽器

let name: string = 'muyy'
let name2: String = 'muyy'複製代碼

Boolen 類型

boolean是 true 或 false 的值,因此 let isBool3: boolean = new Boolean(1) 就會編譯報錯,由於 new Boolean(1) 生成的是一個 Bool 對象。編程語言

let isBool1: boolean = false複製代碼

Number 類型

let number: number = 10;複製代碼

Array 類型

數組是 Array 類型。然而,由於數組是一個集合,咱們還須要指定在數組中的元素的類型。咱們經過 Array<type> or type[] 語法爲數組內的元素指定類型編輯器

let arr:number[] = [1, 2, 3, 4, 5];
let arr2:Array<number> = [1, 2, 3, 4, 5];

let arr3:string[] = ["1","2"];
let arr4:Array<string> = ["1","2"];複製代碼

Enums 類型

列出全部可用值,一個枚舉的默認初始值是0。你能夠調整一開始的範圍:ide

enum Role {Employee = 3, Manager, Admin}
let role: Role = Role.Employee
console.log(role) // 3複製代碼

Any 類型

any 是默認的類型,其類型的變量容許任何類型的值:

let notSure:any = 10;
let notSure2:any[] = [1,"2",false];複製代碼

Void 類型

JavaScript 沒有空值 Void 的概念,在 TypeScirpt 中,能夠用 void 表示沒有任何返回值的函數:

function alertName(): void {
  console.log('My name is muyy')
}複製代碼

函數

爲函數定義類型

咱們能夠給每一個參數添加類型以後再爲函數自己添加返回值類型。 TypeScript可以根據返回語句自動推斷出返回值類型,所以咱們一般省略它。下面函數 add, add2, add3 的效果是同樣的,其中是 add3 函數是函數完整類型。

function add(x: string, y: string): string{
    return "Hello TypeScript";
}

let add2 = function(x: string, y: string): string{
    return "Hello TypeScript";
}

let add3: (x: string, y: string) => string = function(x: string, y: string): string{
    return "Hello TypeScript";
}複製代碼

可選參數和默認參數

JavaScript 裏,每一個參數都是可選的,可傳可不傳。 沒傳參的時候,它的值就是 undefined 。 在 TypeScript 裏咱們能夠在參數名旁使用?實現可選參數的功能。 好比,咱們想讓 lastname 是可選的:

function buildName(firstName: string, lastname?: string){
    console.log(lastname ? firstName + "" + lastname : firstName)
}

let res1 = buildName("鳴","人"); // 鳴人
let res2 = buildName("鳴"); // 鳴
let res3 = buildName("鳴", "人", "君"); // Supplied parameters do not match any signature of call target.複製代碼

若是帶默認值的參數出如今必須參數前面,用戶必須明確的傳入 undefined 值來得到默認值。 例如,咱們重寫上例子,讓 firstName 是帶默認值的參數:

function buildName2(firstName = "鳴", lastName?: string){
    console.log(firstName + "" + lastName)
}

let res4 = buildName2("人"); // undefined人
let res5 = buildName2(undefined, "人"); // 鳴人複製代碼

傳統的JavaScript程序使用函數和基於原型的繼承來建立可重用的組件,但對於熟悉使用面向對象方式的程序員來說就有些棘手,由於他們用的是基於類的繼承而且對象是由類構建出來的。 從ECMAScript 2015,也就是ECMAScript 6開始,JavaScript程序員將可以使用基於類的面向對象的方式。 使用TypeScript,咱們容許開發者如今就使用這些特性,而且編譯後的JavaScript能夠在全部主流瀏覽器和平臺上運行,而不須要等到下個JavaScript版本。

class Person{
    name:string; // 這個是對後文this.name類型的定義
    age:number;
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
    print(){
        return this.name + this.age;
    }
}

let person:Person = new Person('muyy',23)
console.log(person.print()) // muyy23複製代碼

咱們在引用任何一個類成員的時候都用了 this。 它表示咱們訪問的是類的成員。其實這本質上仍是 ES6 的知識,只是在 ES6 的基礎上多上了對 this 字段和引用參數的類型聲明。

繼承

class Person{
    public name:string;  // public、private、static 是 typescript 中的類訪問修飾符
    age:number;
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
    tell(){
        console.log(this.name + this.age);
    }
}

class Student extends Person{
    gender:string;
    constructor(gender:string){
        super("muyy",23);
        this.gender = gender;
    }
    tell(){
        console.log(this.name + this.age + this.gender);
    }
}

var student = new Student("male");
student.tell();  // muyy23male複製代碼

這個例子展現了 TypeScript 中繼承的一些特徵,能夠看到其實也是 ES6 的知識上加上類型聲明。不過這裏多了一個知識點 —— 公共,私有,以及受保護的修飾符。TypeScript 裏,成員默認爲 public ;當成員被標記成 private 時,它就不能在聲明它的類的外部訪問;protected 修飾符與private 修飾符的行爲很類似,但有一點不一樣,protected 成員在派生類中仍然能夠訪問。

存儲器

TypeScript 支持經過 getters/setters 來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問。

對於存取器有下面幾點須要注意的:
首先,存取器要求你將編譯器設置爲輸出 ECMAScript 5 或更高。 不支持降級到 ECMAScript 3。 其次,只帶有 get 不帶有 set 的存取器自動被推斷爲 readonly。 這在從代碼生成 .d.ts 文件時是有幫助的,由於利用這個屬性的用戶會看到不容許夠改變它的值。

class Hello{
    private _name: string;
    private _age: number;
    get name(): string {
        return this._name;
    }
    set name(value: string) {
        this._name = value;
    }
    get age(): number{
        return this._age;
    }
    set age(age: number) {
        if(age>0 && age<100){
            console.log("年齡在0-100之間"); // 年齡在0-100之間
            return;
        }
        this._age = age;
    }
}

let hello = new Hello();
hello.name = "muyy";
hello.age = 23
console.log(hello.name); // muyy複製代碼

接口

接口

TypeScript的核心原則之一是對值所具備的結構進行類型檢查。在TypeScript裏,接口的做用就是爲這些類型命名和爲你的代碼或第三方代碼定義契約。

interface LabelValue{
    label: string;
}

function printLabel(labelObj: LabelValue){
    console.log(labelObj.label);
}

let myObj = {
    "label":"hello Interface"
};
printLabel(myObj);複製代碼

LabelledValue 接口就比如一個名字,它表明了有一個 label 屬性且類型爲 string 的對象。只要傳入的對象知足上述必要條件,那麼它就是被容許的。

另外,類型檢查器不會去檢查屬性的順序,只要相應的屬性存在而且類型也是對的就能夠。

可選屬性

帶有可選屬性的接口與普通的接口定義差很少,只是在可選屬性名字定義的後面加一個 ? 符號。可選屬性的好處之一是能夠對可能存在的屬性進行預約義,好處之二是能夠捕獲引用了不存在的屬性時的錯誤。

interface Person{
    name?:string;
    age?:number;
}

function printInfo(info:Person){
    console.log(info);
}

let info = {
    "name":"muyy",
    "age":23
};

printInfo(info); // {"name": "muyy", "age": 23}

let info2 = {
    "name":"muyy"
};

printInfo(info2); // {"name": "muyy"}複製代碼

函數類型

接口可以描述 JavaScript 中對象擁有的各類各樣的外形。 除了描述帶有屬性的普通對象外,接口也能夠描述函數類型。定義的函數類型接口就像是一個只有參數列表和返回值類型的函數定義。參數列表裏的每一個參數都須要名字和類型。定義後完成後,咱們能夠像使用其它接口同樣使用這個函數類型的接口。

interface SearchFunc{
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string,subString: string){
    return source.search(subString) !== -1;
};

console.log(mySearch("鳴人","鳴")); // true
console.log(mySearch("鳴人","纓")); // false複製代碼

可索引類型

與使用接口描述函數類型差很少,咱們也能夠描述那些可以「經過索引獲得」的類型,好比 a[10]ageMap["daniel"]。 可索引類型具備一個索引簽名,它描述了對象索引的類型,還有相應的索引返回值類型。 讓咱們看以下例子:

interface StringArray{
    [index: number]: string;
}

let MyArray: StringArray;
MyArray = ["是","雲","隨","風"];
console.log(MyArray[2]); // 隨複製代碼

類類型

與 C# 或 Java 裏接口的基本做用同樣,TypeScript 也可以用它來明確的強制一個類去符合某種契約。

咱們能夠在接口中描述一個方法,在類裏實現它,如同下面的 setTime 方法同樣:

interface ClockInterface{
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface{
    currentTime: Date;
    setTime(d: Date){
        this.currentTime = d;
    }
    constructor(h: number, m: number) {}
}複製代碼

繼承接口

和類同樣,接口也能夠相互繼承。 這讓咱們可以從一個接口裏複製成員到另外一個接口裏,能夠更靈活地將接口分割到可重用的模塊裏。

interface Shape{
    color: string;
}

interface PenStroke{
    penWidth: number;
}

interface Square extends Shape,PenStroke{
    sideLength: number;
}

let s = <Square>{};
s.color = "blue";
s.penWidth = 100;
s.sideLength = 10;複製代碼

模塊

TypeScript 與 ECMAScript 2015 同樣,任何包含頂級 import 或者 export 的文件都被當成一個模塊。

export interface StringValidator{
    isAcceptable(s:string): boolean;
}

var strReg = /^[A-Za-z]+$/;
var numReg = /^[0-9]+$/;

export class letterValidator implements StringValidator{
    isAcceptable(s:string): boolean{
        return strReg.test(s);
    }
}

export class zipCode implements StringValidator{
    isAcceptable(s: string): boolean{
        return s.length == 5 && numReg.test(s);
    }
}複製代碼

泛型

軟件工程中,咱們不只要建立一致的定義良好的 API ,同時也要考慮可重用性。 組件不只可以支持當前的數據類型,同時也能支持將來的數據類型,這在建立大型系統時爲你提供了十分靈活的功能。
在像 C# 和 Java 這樣的語言中,可使用泛型來建立可重用的組件,一個組件能夠支持多種類型的數據。 這樣用戶就能夠以本身的數據類型來使用組件。

初探泛型

以下代碼,咱們給 Hello 函數添加了類型變量 T ,T 幫助咱們捕獲用戶傳入的類型(好比:string)。咱們把這個版本的 Hello 函數叫作泛型,由於它能夠適用於多個類型。 代碼中 outputoutput2 是效果是相同的,第二種方法更加廣泛,利用了類型推論 —— 即編譯器會根據傳入的參數自動地幫助咱們肯定T的類型:

function Hello<T>(arg:T):T{
    return arg;
}

let outPut = Hello<string>('Hello Generic');
let output2 = Hello('Hello Generic')

console.log(outPut);
console.log(outPut2);複製代碼

參考資料

相關文章
相關標籤/搜索