碼文不易,轉載請帶上本文連結,感謝~ https://www.cnblogs.com/echoyya/p/14542005.html
TypeScript 介紹
首先介紹一下什麼是TypeScript ,與JavaScript的區別,及優缺點
什麼是TypeScript?
- 是新增了型別系統的 JavaScript,適用於任何規模的專案。
- 是一門靜態型別、弱型別的語言。
- 完全相容 JavaScript,且不會修改 JavaScript 執行時的特性。
- 可以編譯為 JavaScript,然後執行在瀏覽器、Node.js 等任何能執行 JavaScript 的環境中。
- 擁有很多編譯選項,型別檢查的嚴格程度可通過配置檔案決定。
- 可以和 JavaScript 共存,這意味著 JavaScript 專案能夠漸進式的遷移到 TypeScript。
- 增強了編輯器(IDE)的功能,提供了程式碼補全、介面提示、跳轉到定義、程式碼重構等能力。
- 擁有活躍的社群,大多數常用的第三方庫都提供了型別宣告,並且開源免費
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
|0B
和0o
|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; // 編譯通過
任意值
任意值(Any)用來表示允許賦值為任意型別
。一個普通型別,在賦值過程中是不被允許改變型別的,any 型別,允許被賦值為任意型別。
let str: string = 'abc';
str = 123;
// Type 'number' is not assignable to type 'string'.
// -----------------------------------------------------------------
let str: any = 'abc';
str = 123;
// 編譯通過
任意值可以訪問任意屬性和方法:
let anyThing: any = 'hello';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
// 編譯通過
未宣告型別的變數
:如果變數在宣告時,未指定型別,那麼會被識別為任意值型別:
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];
後續筆記敬請期待~~~