變數型別的那些事
1.基本註解
型別註解使用:TypeAnnotation
語法。型別宣告空間中可用的任何內容都可以用作型別註解。
const num: number = 123;
function identity(num:number):number{
return num;
}
複製程式碼
加入註解以後會報錯的寫法:
const num: number = 123;
function identity(num: number): number {
const num1 = '123'
// 返回的不是 number 報錯
return num1;
}
const num1 = '123'
// 引數不是 number 報錯
identity(num1)
複製程式碼
2. 原始型別
Javascript
原始型別也同樣適用於 TypeScript
的型別系統。因此,string
, number
,boolean
也可以被用作型別註解:
let num: number;
let str: string;
let bool: boolean;
num = 123;
num = 123.45;
num = '123'; //Type '"123"' is not assignable to type 'number'
str = '123';
str = 123; //Type '123' is not assignable to type 'string'
bool = true;
bool = false;
bool = 'false';//Type '"false"' is not assignable to type 'boolean'.
複製程式碼
3. 陣列
TypeScript
為陣列提供了專用的型別語法,因此你可以很輕易的註解陣列。它使用字尾[]
,接著你可以根據需要補充任何有效的型別註解(如:boolean[]
)。它能讓你安全的使用任何有關陣列的操作,而且它能放置一些類似賦值錯誤型別給成員的行為。
你有兩種定義陣列的方式:
第一種,你可以在元素型別後面接上[]
,表示由此型別元素組成的一個陣列:
let list:number[] = [1,2,3]
複製程式碼
第二種方式是使用陣列泛型,Array<元素型別>
:
let list: Array<number> = [1,2,3]
複製程式碼
一個具體的栗子:
const boolArray: boolean[];
boolArray = [true, false];
console.log(boolArray[0]); // true
console.log(boolArray.length); // 2
boolArray[1] = true;
boolArray = [false, false];
boolArray[0] = 'false'; // Error
boolArray = 'false'; // Error
boolArray = [true, 'false']; // Error
複製程式碼
4.元組
元組型別允許表示一個已知元素數量和型別的陣列,各元素的型別不必相同。比如,你可以定義一對值分別為string
和number
型別的元組。
// 宣告一個元組型別
let x: [string, number];
// 初始化賦值
x = ['hello', 10]; // OK
// 初始化錯誤
x = [10, 'hello']; // Error
複製程式碼
當訪問一個已知索引的元素,會得到正確的型別:
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
複製程式碼
????????? 當訪問一個越界的元素,會使用聯合型別替代:
x[3] = 'world'; // OK, 字串可以賦值給(string|number)型別
複製程式碼
但是我在編輯器裡使用的時候是報錯的,Index '2' is out-of-bounds in tuple of length 2,我也不知道為什麼??
5. 列舉
enum
型別是對JavaScript
標準資料型別的一個補充。使用列舉型別可以為一組數值賦予友好的名字。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
複製程式碼
預設情況下,從0開始為元素編號。你也可以手動的指定成員的編號。例如,我們將上面的栗子改成從1開始編號:
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
複製程式碼
或者採用全部手動賦值:
enum Color {Red = 1,Geeen = 2, Blue = 4}
let c: Color = Color.Green
複製程式碼
編譯後的js
var Color;
(function (Color) {
Color[Color["Red"] = 1] = "Red";
Color[Color["Green"] = 2] = "Green";
Color[Color["Blue"] = 4] = "Blue";
})(Color || (Color = {}));
var c = Color.Green;
複製程式碼
所有的表示式都有返回值,它的返回值就是等號右邊的賦值。
列舉型別提供的一個遍歷是你可以由列舉的值得到它的名字。 例如,我們知道數值為2,但是不確定它對映到Color裡的哪個名字,我們可以查詢相應的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // 顯示'Green'因為上面程式碼裡它的值是2
複製程式碼
這個試了一下不給數值number,發現沒辦法通過這個取值
enum Color {Red = 'r', Green = 'g', Blue = 'b'}
let c: Color = Color.Green;
console.log(c) // g
let colorName:string = Color[2]
console.log(colorName) // undefined
複製程式碼
我們看一下編譯後的javascript
程式碼就明白了:
var Color;
(function (Color) {
Color["Red"] = "r";
Color["Green"] = "g";
Color["Blue"] = "b";
})(Color || (Color = {}));
var c = Color.Green;
console.log(c);
var colorName = Color[2];
console.log(colorName);
複製程式碼
6. 特殊型別
6.1 any
any
型別在 TypeScript
型別系統中佔有特殊的地位。它提供給你一個型別系統的【後門】,TypeScript
將會把型別檢查關閉。在型別系統裡any
能夠相容所有的型別(包括它自己)。因此,所有的型別都能夠被賦值給它。它也能被賦值給其他任何型別。
let power: any;
//賦值任意型別
power = '123'
power = 123
let num:number;
num = power;
power = num;
複製程式碼
6.2 null 和 undefined
在型別系統中,JavaScript 中的 null 和 undefined 字面量和其他被標註了 any 型別的變數一樣,都能被賦值給任意型別的變數,如下例子所示:
let num: numer;
let str: string;
// 這些型別能被賦予
num = null;
str = undefined;
複製程式碼
6.3 void
使用 :void
來表示一個函式沒有一個返回值
function log(message:string):void{
console.log(message)
}
複製程式碼
宣告一個void
型別的變數沒有什麼大用,因為你只能為它賦予undefined
和null
:
let unusable:void = undefined
複製程式碼
7.泛型
軟體工程中,我們不僅要建立一致的定義良好的API,同時也要考慮可重用性。
不僅能夠支援當前的資料型別,同時也能支援未來的資料型別,這在建立大型系統時為你提供了十分靈活的功能。
舉一個簡單的演變例子:
當不實用泛型的時候,你的程式碼可能是像這樣:
function identity(arg:number):number{
return arg;
}
複製程式碼
這個函式可以接收number
型別,返回的也是number
型別,考慮到這個函式的可重用性,或者,我們可以使用any
型別來定義函式像這樣:
function identity(arg:any):any{
return arg;
}
複製程式碼
使用any
型別會導致這個函式可以接受任何型別的arg
引數,且任何型別的值都可能被返回。
我們需要一種方法使返回值的型別與傳入引數的型別是相同的。 接下來我們可以把程式碼像下面這樣:
function identity<T>(arg:T):T{
return arg
}
複製程式碼
我們給 identity
新增了型別變數 T
。 如果傳入的型別(比如:number
),我們就可以知道返回的型別也是number
。現在我們就可以知道引數型別與返回值型別是相同的了。這有助於我們跟蹤函式裡使用的型別的資訊。
我們把這個版本的identity
函式叫做 泛型。 它可以使用與多個型別,不同於使用any
,的不確定性,還能保持像第一個例子一樣的準確性,引數型別
與返回值型別
是相同
的。
7.1 泛型的使用
我們定義了泛型函式之火,可以使用兩種方法使用。
第一種是傳入所有的引數,包含型別引數:
let output = identity<string>('myString');//type of output will be 'string'
複製程式碼
這裡我們明確的指定了T
是string
型別,並做為一個引數傳給函式,並且確定output
的型別是string
.
注意⚠️:使用<>
不是()
第二種方法,利用型別推論--即編譯器會根據傳入的引數自動的幫助我們確定T
的型別。
let output = identity("myString");// type of output will be 'string'
複製程式碼
這裡並沒有使用(<>
)來明確地傳入型別;編譯器可以檢視myString
的值,然後把T
設定為它的型別。
7.2 使用泛型變數
使用泛型建立像identity
這樣的泛型函式時,編譯器要求你在函式體必須正確的使用這個通用的型別。換句話說,你必須把這些引數當作時任意或所有型別。
看下我們之前寫的泛型例子:
function identity<T>(arg:T):T{
return arg
}
複製程式碼
當我們試圖在函式內輸出arg.length
的時候,編譯器就會報錯型別“T”上不存在屬性“length”
function idenfity<T>(arg:T):T{
console.log(arg.length) // Error: T doesn't have .length
return arg
}
複製程式碼
注意⚠️:
型別變數T
代表的時任意型別,所以使用這個函式的人可能傳入的是個數字,而數字時沒有.length
屬性的。
然後我們假設想操作T
型別的陣列而不直接是T
,由於我們操作的是陣列,此時.length
的屬性應該是存在。
程式碼像這樣:
function loggingIdentity<T>(arg: T[]):T[]{
console.log(arg.length) // Array has a .length,so no more error
return arg
}
複製程式碼
我們可以這樣理解loggingIdentity
的型別:泛型函式loggingIdentity
,接收型別引數T
和引數arg
,它是個元素型別是T
的陣列,並返回元素型別T
的陣列。如果我們傳入數字陣列,將返回一個數字陣列,因為此時T
的型別為number
。這可以讓我們把泛型變數T
當作型別的一部分使用,而不是整個型別,增加了靈活性。
我們還可以這樣實現上面的例子:
function loggingIdentity<T>(arg:Array<T>):Array<T>{
console.log(arg.length)
return arg
}
複製程式碼
在電腦科學中,許多演算法和資料結構並不會依賴於物件的實際型別。然而,你仍然會想在每個變數裡強制提供約束。
例如:在一個函式中,它接受一個列表,並且返回這個列表的反向排序,這裡的約束是指傳入至函式的引數與函式的返回值:
function reverse<T>(items:T[]):T[]{
const toreturn = []
for(let i = items.length - 1;i>=0;i--){
toreturn.push(items[i])
}
return toreturn
}
const sample = [1,2,3]
let reversed = reverse(sample)
console.log(reversed)
reversed[0] = '1'; // Error
reversed = ['1', '2']; // Error
reversed[0] = 1; // ok
reversed = [1, 2]; // ok
複製程式碼
這個栗子中,函式reverse
接受一個型別為T
的陣列items:T[]
,返回值為型別T
的一個陣列(注意:T[]
),函式 reverse
的返回值型別與它接受的引數型別一樣。當你傳入var sample = [1, 2, 3]
時,TypeScript
能推斷出 reverse
為number[]
型別,從而能給你型別安全。於此相似,當你傳遞一個string[]型別的陣列時,TypeScript
能推斷出為string[]
型別,
如:
const strArr = ['1','2']
let reversedStrs = reverse(strArr)
reversedStrs = [1, 2]; // Error
複製程式碼
如果你的陣列是const sample = [1,false,3]
等同於const sample: (number | boolean)[]
,所以下面的程式碼也能可以的,如果是陣列裡有兩種甚至三種型別的時候,它是能夠推斷出(number | boolean)
這種或的型別的。也就是下面所說的聯合型別。
function reverse<T>(items:T[]):T[]{
const toreturn = []
for(let i = items.length - 1;i>=0;i--){
toreturn.push(items[i])
}
return toreturn
}
const sample = [1,false,3]
let reversed = reverse(sample)
console.log(reversed)
reversed[0] = true; // OK
reversed = [3, true]; // OK
reversed[0] = 1; // ok
reversed = [1, 2]; // ok
複製程式碼
8.聯合型別
聯合型別表示取值可以為多種型別中的一種。
8.1簡單的例子
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'
myFavoriteNumber = 7
複製程式碼
let myFavoriteNumber: string | number;
myFavoriteNumber = true;
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'
複製程式碼
聯合型別使用|
分隔每個型別。
這裡的let myFavoriteNumber: string | number;
的含義是,允許myFavoriteNumber
的型別是string
或者number
,但是不能是其他型別。
8.2 訪問聯合型別的屬性或方法
當TypeScript
不確定一個聯合型別的變數到底是哪個型別的時候,我們只能訪問此聯合型別的所有型別裡的共有的屬性或方法。
function getLength(something:string|number):number{
return something.length
}
// Error 型別“string | number”上不存在屬性“length”。
型別“number”上不存在屬性“length”。
複製程式碼
上例中,length
不是string
和number
的共有屬性,所以會報錯。
訪問string
和number
的共有屬性是沒問題的:
function getLength(something:string|number):string{
return something.toString()
}
複製程式碼
聯合型別的變數在被賦值的時候,會根據型別推論的規則推斷出一個型別:
let myFavoriteNumber:string|number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length)
myFavoriteNumber = 7
console.log(myFavoriteNumber.length) // Rrror 型別“number”上不存在屬性“length”。
複製程式碼
上例中,第二行的myFavoriteNumber
被推斷成了string
,訪問它的length
屬性不會報錯。而第四行的myFavoriteNumber
被推斷成了number
,訪問它的length
屬性時就報錯了。
一個常見的用例是一個可以接受單個物件或者物件陣列的函式:
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
// Do stuff with line: string
}
複製程式碼