TypeScript 入門自學筆記(一)

Echoyya、發表於2021-03-16

碼文不易,轉載請帶上本文連結,感謝~ https://www.cnblogs.com/echoyya/p/14542005.html

TypeScript 介紹

首先介紹一下什麼是TypeScript ,與JavaScript的區別,及優缺點

什麼是TypeScript?

  1. 是新增了型別系統的 JavaScript,適用於任何規模的專案。
  2. 是一門靜態型別、弱型別的語言。
  3. 完全相容 JavaScript,且不會修改 JavaScript 執行時的特性。
  4. 可以編譯為 JavaScript,然後執行在瀏覽器、Node.js 等任何能執行 JavaScript 的環境中。
  5. 擁有很多編譯選項,型別檢查的嚴格程度可通過配置檔案決定。
  6. 可以和 JavaScript 共存,這意味著 JavaScript 專案能夠漸進式的遷移到 TypeScript。
  7. 增強了編輯器(IDE)的功能,提供了程式碼補全、介面提示、跳轉到定義、程式碼重構等能力。
  8. 擁有活躍的社群,大多數常用的第三方庫都提供了型別宣告,並且開源免費

JavaScript 的缺點

首先JavaScript 是一門非常靈活的程式語言:

  • 它沒有型別約束,一個變數可能初始化時是字串,又被賦值為數字。
  • 由於隱式型別轉換的存在,有些變數的型別很難在執行前就確定。
  • 基於原型的物件導向程式設計,使得原型上的屬性或方法可以在執行時被修改。

TypeScript 的型別系統,在很大程度上彌補了 JavaScript 的缺點。

為什麼使用 TypeScript?

一般來說,在大型專案中,後期維護成本比前期開發成本要多得多,所以團隊規範化尤為重要,包括編碼規範,方法呼叫規範等,而TS可以通過程式碼的方式,約束團隊開發,這樣才有利於後期維護及擴充套件,從而達到高效的開發

兩個最重要的特性——型別系統適用於任何規模

優勢:強大的IDE支援,支援型別檢測,允許為變數指定型別,語法檢測,語法提示

缺點:有一定的學習成本,需要理解 介面,泛型,類,列舉型別等前端可能不是很熟悉的知識點。

介面(Interfaces):可以用於對``物件的形狀Shape`進行描述

泛型(Generics):在定義函式,介面或類時,不預先指定具體的型別,而是在使用時在指定型別的一種特性

類(Classes):定義了一件事物的抽象特點,包括屬性和方法

安裝

若想使用TS進行開發,首先必須要搭建搭建TypeScript開發環境
安裝: npm install -g typescript,全域性安裝,可以在任意位置執行tsc

版本:tsc -v

編譯:tsc 檔名.ts

TS 中,使用:為變數指定型別,: 前後的空格可有可無。TS 只會在編譯時對型別進行靜態檢查,如發現有錯誤,編譯時就會報錯。而在執行時,與普通的 JavaScript 檔案一樣,不會對型別進行檢查。

編譯時即使報錯,還是會生成編譯結果,仍然可以使用編譯之後的檔案,若想在報錯時終止 js檔案的生成,可以在 tsconfig.json 中配置 noEmitOnError 即可。

TypeScript 的特性

型別系統按照型別檢查的時機分類,可以分為動態型別靜態型別

型別系統

TypeScript 是靜態型別

動態型別:是指在執行時才會進行型別檢查,型別錯誤往往會導致執行時錯誤。JavaScript 是一門解釋型語言,沒有編譯階段,所以它是動態型別,以下程式碼在執行時才會報錯:

// test.js
let foo = 1;
foo.split(' ');

// TypeError: foo.split is not a function  執行時會報錯(foo.split 不是一個函式)

靜態型別:是指編譯階段就能確定每個變數的型別,型別錯誤往往會導致語法錯誤。TypeScript 在執行前需要先編譯為 JavaScript,而在編譯階段就會進行型別檢查,所以 TypeScript 是靜態型別,以下程式碼在編譯階段就會報錯:

// test.ts
let foo: number = 1;
foo.split(' ');

// Property 'split' does not exist on type 'number'. 編譯時報錯(數字沒有 split 方法),無法通過編譯

TypeScript 是弱型別

型別系統按照是否允許隱式型別轉換分類,可以分為強型別弱型別

以下程式碼在 JS或 TS 中都可以正常執行,執行時數字 1 會被隱式型別轉換為字串 '1',加號 + 被識別為字串拼接,所以列印出結果是字串 '11'。

console.log(1 + '1');   // 11

TS 是完全相容 JS的,並不會修改 JS 執行時的特性,所以它們都是弱型別。雖然 TS 不限制加號兩側的型別,但是可以藉助型別系統,以及 ESLint 程式碼檢查,來限制加號兩側必須同為數字或同為字串。會在一定程度上使得 TypeScript 向強型別更近一步了——當然,這種限制是可選的。

這樣的型別系統體現了 TypeScript 的核心設計理念:在完整保留 JavaScript 執行時行為的基礎上,通過引入靜態型別系統來提高程式碼的可維護性,減少可能出現的 bug。

原始資料型別基本使用

布林值、數值、字串、null、undefined,為變數指定型別,且變數值需與型別一致

let flag: boolean = false
let num: number = 15
let str: string = 'abc'
var a: null = null
var b: undefined = undefined

// 編譯通過

使用建構函式創造的物件不是原始資料型別,事實上 new XXX() 返回的是一個 XXX物件

let flag:boolean=new Boolean(false)  // Type 'Boolean' is not assignable to type 'boolean'.
let num: number = new Number(15)     // Type 'Number' is not assignable to type 'number'.
let str: string = new String('abc')  // Type 'String' is not assignable to type 'string'.

// 編譯通過

但是可以直接呼叫 XXX 也可以返回一個 xxx 型別

  let flag: boolean = Boolean(false)
  let num : number  = Number(15)
  let str : string  = String('abc')
  
  // 編譯通過

數值

使用 number 定義數值型別:

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;  // ES6 中的二進位制表示法
let octalLiteral: number = 0o744;    // ES6 中的八進位制表示法
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

編譯結果:

var decLiteral = 6;
var hexLiteral = 0xf00d;
var binaryLiteral = 10; // ES6 中的二進位制表示法
var octalLiteral = 484; // ES6 中的八進位制表示法
var notANumber = NaN;
var infinityNumber = Infinity;

ES6 中二進位制和八進位制數值表示法:分別用字首0b|0B0o|0O表示。會被編譯為十進位制數字。

字串

使用string定義字串型別:

let myName: string = 'Echoyya';
let str: string = `Hello, my name is ${myName}.`; // 模板字串

編譯結果:

var myName = 'Echoyya';
var str = "Hello, my name is " + myName + "."; // 模板字串

ES6 中模板字串:增強版的字串,用反引號(`) 標識 ${expr} 用來在模板字串中嵌入表示式。

空值 及(與Null 和 Undefined的區別)

JavaScript 沒有空值(Void)的概念,在 TS中,用 void 表示沒有任何返回值的函式:

function alertName(): void {
    alert('My name is Tom');
}

然而宣告一個 void 型別的變數沒什麼用,因為只能將其賦值為 undefined 和 null:

let unusable: void = undefined;

Null 和 Undefined

let u: undefined = undefined;
let n: null = null;

區別:undefined 和 null 是所有型別的子型別。也就是說 undefined 型別的變數,可以賦值給所有型別的變數,包括 void 型別:

let num: number = undefined;

let u: undefined;
let str: string = u;
let vo: void= u;

// 編譯通過

而 void 型別的變數不能賦值給其他型別的變數,只能賦值給 void 型別:

let u: void;
let num: number = u;  // Type 'void' is not assignable to type 'number'.
let vo: void = u;     // 編譯通過

任意值

  1. 任意值(Any)用來表示允許賦值為任意型別。一個普通型別,在賦值過程中是不被允許改變型別的,any 型別,允許被賦值為任意型別。
let str: string = 'abc';
str = 123;
 
// Type 'number' is not assignable to type 'string'.
  
// -----------------------------------------------------------------
  
let str: any = 'abc';
str = 123;
  
// 編譯通過
  1. 任意值可以訪問任意屬性和方法:
let anyThing: any = 'hello';
   
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
   
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
   
// 編譯通過
  1. 未宣告型別的變數:如果變數在宣告時,未指定型別,那麼會被識別為任意值型別:
let something;
something = 'seven';
something = 7;

// 等價於
   
let something: any;
something = 'seven';
something = 7;
   

型別推論

若未明確指定型別,那麼 TS 會依照型別推論(Type Inference)規則推斷出一個型別。
以下程式碼雖然沒有指定型別,但編譯時會報錯:

let data = 'seven';
data = 7;

// Type 'number' is not assignable to type 'string'.

事實上,它等價於:

let data: string = 'seven';
data = 7;

// Type 'number' is not assignable to type 'string'.

TS會在未明確指定型別時推測出一個型別,這就是型別推論。如果定義時未賦值,不管之後是否賦值,都會被推斷成 any 型別

let data;
data = 'seven';
data = 7;

// 編譯通過

聯合型別

聯合型別(Union Types)表示取值可以為多種型別中的一種,使用 | 分隔每個型別。

let data: string | number;   // 允許 data 可以是 string 或 number 型別,但不能是其他型別
data = 'seven';
data = 7;

data = true;

//  Type 'boolean' is not assignable to type 'string | number'.

訪問聯合型別的屬性或方法:當不確定一個聯合型別的變數到底是哪個型別時,只能訪問此聯合型別中所有型別共有的屬性或方法

function getLength(something: string | number): number {
    return something.length;
}

// length 不是 string 和 number 的共有屬性,所以會報錯
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

訪問 string 和 number 的共有屬性:

function getString(something: string | number): string {
    return something.toString();
}

聯合型別的變數在被賦值的時候,會根據型別推論的規則推斷出一個型別:

let data: string | number;
data = 'seven';
console.log(data.length); // 5
data = 7;
console.log(data.length); // 編譯時報錯

// Property 'length' does not exist on type 'number'.

line2:data 被推斷為 string,訪問length 屬性不會報錯。

line4:data 被推斷為 number,訪問length 屬性報錯。

物件的型別——介面

在 TS中,使用介面(Interfaces)來定義物件的型別。可用於對類的一部分行為進行抽象以外,也常用於對物件的形狀(Shape)進行描述。

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

let p1: Person = {
    name: 'Tom',
    age: 25
};

定義一個介面 Person(介面一般首字母大寫),定義一個變數 tom 型別是 Person。這樣就約束了 tom 的形狀必須和介面 Person 一致。

確定屬性

確定屬性:賦值時,定義的變數的形狀必須與介面形狀保持一致。變數的屬性比介面少或多屬性都是不允許的:

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

let p1: Person = {  // 缺少 age 屬性
    name: 'Tom'
};

// Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.


// -----------------------------------------------------------------


let p2 Person = {  // 多餘 gender 屬性
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

可選屬性

可選屬性:是指該屬性可以不存在。有時不需要完全匹配一個介面時,可以用可選屬性,但此時仍然不允許新增未定義的屬性

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

let p1: Person = {  // 編譯通過
    name: 'Tom'
};

let p2: Person = {  // 編譯通過
    name: 'Tom',
    age: 25
};

let p3: Person = {  // 報錯(同上)
    name: 'Tom',
    age: 25,
    gender: 'male'
};

任意屬性

任意屬性:允許一個介面有任意的屬性

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let p1: Person = {
    name: 'Tom',
    gender: 'male'
};

使用 [propName: string] 定義了任意屬性的屬性名取 string 型別的值。屬性值為任意值

注意:一旦定義了任意屬性,那麼確定屬性和可選屬性的型別都必須是它的型別的子集:

例一:任意屬性的型別是 string,但是可選屬性 age 的值卻是 number,number 不是 string 的子屬性,所以會報錯。

interface Person {
    name: string;
    age?: number;  
    [propName: string]: string;
}

let p1: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
 
// Property 'age' of type 'number' is not assignable to string index type 'string
// Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Property 'age' is incompatible with index signature.
// Type 'number' is not assignable to type 'string'

例二:一個介面中只能定義一個任意屬性。如果介面中有多個型別的屬性,可以在任意屬性中使用聯合型別

interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
}

let p1: Person = { // 編譯通過
    name: 'Tom',
    age: 25,
    gender: 'male',
    year:2021
};

只讀屬性

物件中的一些欄位只能在建立時被賦值,可以使用 **readonly **定義只讀屬性

例一:使用 readonly 定義的屬性 id 初始化後,又被重新賦值,所以會報錯。

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let p1: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

p1.id = 9527;

// Cannot assign to 'id' because it is a read-only property.

例二:只讀的約束存在於第一次給物件賦值的時候,而不是第一次給只讀屬性賦值時:

 interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let p2: Person = {  // 第一次給物件賦值
    name: 'Tom',
    gender: 'male'
};

p2.id = 89757;

// Property 'id' is missing in type '{ name: string; gender: string; }' but required in type 'Person'  對 p2 賦值時,沒有給 id 賦值

// Cannot assign to 'id' because it is a read-only property.  id 是隻讀屬性

陣列的型別

在 TS 中,有多種定義陣列型別的方式。

型別 + 方括號 表示法

最簡單的方法是使用型別 + 方括號來表示陣列:

let arr: number[] = [1, 1, 2];    // 陣列元素中不允許出現其他的型別

let arr1: number[] = [1, '1', 2]; // 報錯:Type 'string' is not assignable to type 'number'.

陣列的一些方法的引數也會根據陣列在定義時約定的型別進行限制:

let arr2: number[] = [1, 1, 2, 3, 5];

arr2.push('8'); 

//報錯:Argument of type '"8"' is not assignable to parameter of type 'number'.

陣列泛型

使用陣列泛型(Array Generic) Array<elemType> 來表示陣列:

let arr3: Array<number> = [1, 1, 2, 3, 5];

泛型涉及內容較多,後期有時間會在整理一篇文章,敬請關注!

用介面表示陣列

之前介紹了使用介面表示物件的型別,同樣介面也可以用來描述陣列:

interface NumberArray {
    [index: number]: number;
}
let arr: NumberArray = [1, 1, 2, 3, 5];

NumberArray 表示:索引的型別是數字,值的型別也是數字,這樣便可以表示一個數字型別的陣列,雖然介面也可以描述陣列,但是一般不會這麼做,因為這種方式較複雜。有一例外,就是常用來表示類陣列。

類陣列

類陣列(Array-like Object)不是陣列型別,比如 arguments,實際上是一個類陣列,不能用普通陣列的方式來描述,而應該用介面:

function sum() {
    let args: number[] = arguments;
}

// 報錯:Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.

介面描述類陣列:除了約束索引的型別是數字,值的型別也必須是數字之外,也約束了它還有 length 和 callee 兩個屬性。

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

而事實上常用的類陣列都有自己的介面定義,如 IArguments, NodeList, HTMLCollection 等:

function sum() {
    let args: IArguments = arguments;
}

//其中 IArguments 是 TypeScript 中定義好了的型別,它實際上就是:
interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

any 在陣列中的應用

一個比較常見的做法是,用 any 表示陣列中允許出現任意型別:

let list: any[] = ['Echoyya', 25, { website: 'https://www.cnblogs.com/echoyya/' }, false];

後續筆記敬請期待~~~

相關文章