這次在上次寫的TypeScript基礎語法基礎上,作了一些補充,接着是語法的進階。數組
// 數組寫法1 const arr2: (number | string)[] = [1, 2, 'abe', 3]; // 數組寫法2 const arr1: Array<number | string> = ['abcd', 2, 3];
// 枚舉 enum Size { Small, Middle, Large, XLarge } console.log(Size.Small, Size.Middle, Size.Large, Size.XLarge); // 0 1 2 3
也能夠自定義枚舉值。閉包
// 自定義枚舉值 enum Size { Small = 1, Middle, Large = 7, XLarge } console.log(Size.Small, Size.Middle, Size.Large, Size.XLarge); // 1 2 7 8
反向映射。dom
// 反向映射 enum Size { Small = 1, Middle, Large = 7, XLarge } console.log(Size[1], Size[2], Size[7], Size[8]); /** * 獲取索引對應的屬性,若是是自定義的索引,要根據自定義的來獲取 * Small Middle Large XLarge */
// object let obj = { name: 'Tom' }; function getName(obj: object): void { console.log(obj); } getName(obj); // { name: "Tom" }
const s = Symbol(); console.log(s); // Symbol() const s3 = Symbol('abcd'); console.log(s3); // Symbol(abcd) const s4 = Symbol('abcd'); console.log(s4); // Symbol(abcd) // 雖然s3和s4看起來同樣,但比較,s3!=s4 // Symbol值不能用於運算,可是能夠轉化爲boolean值或者string值 console.log(s4.toString()); // "Symbol(abcd)" console.log(!s4); // false console.log(Boolean(s4)); // true
Symbol通常用來標記屬性名的惟一性。ide
// 對象屬性舉例 let prop = 'name'; const obj2 = { name: 'Jerry', [`my_${ prop }_is`]: 'Tom' }; console.log(obj2); // {name: "Jerry", my_name_is: "Tom"}
// Symbol做爲屬性名 const s5 = Symbol('name'); const obj3 = { age: 23, [s5]: 'Tom' } console.log(obj3); // {age: 23, Symbol(name): "Tom"} // 獲取對象的屬性,直接obj.s5獲取不到Symbol類型的屬性值,須要使用[]獲取 console.log(obj3.s5); // error console.log(obj3[s5]); // name
如下這些都是沒法獲取到Symbol類型屬性名的狀況。函數
// 一、for in循環不出Symbol值屬性名 const s5 = Symbol('name'); const obj3 = { age: 23, [s5]: 'Tom' } for (let key in obj3) { console.log(key); } // age // 二、Object.keys(obj3)也無法獲取Symbol值屬性名 console.log(Object.keys(obj3)); // ["age"] // 三、Object.getOwnPropertyNames(obj3)也沒法獲取Symbol值屬性名 console.log(Object.getOwnPropertyNames(obj3)); // ["age"] // 四、JSON.stringify把一個對象轉化爲字符串,也沒法獲取Symbol 值屬性名 console.log(JSON.stringify(obj3)); // '{"age":23}'
如下這些是能夠獲取到Symbol類型屬性名的狀況this
// 一、Object.getOwnPropertySymbols(obj3)會返回對象中全部Symbol 值屬性名 console.log(Object.getOwnPropertySymbols(obj3)); // [Symbol(name)] // 二、Reflect.ownKeys(obj3)會返回包含Symbol值在內的屬性名 console.log(Reflect.ownKeys(obj3)); // ["age", Symbol(name)]
const s6 = Symbol('Tom'); const s7 = Symbol('Tom'); // s6 === s7 false const s8 = Symbol.for('Tom'); const s9 = Symbol.for('Tom'); // s8 === s9 true
// Symbol.keyFor() 與Symbol.for()對應, // Symbol.keyFor()能獲取Symbol.for()的屬性, // 但不能獲取單純的Symbol()的屬性 console.log(Symbol.keyFor(s6)); // undefined console.log(Symbol.keyFor(s8)); // Tom
類型斷言指的是強行把一個變量類型指定爲所須要的類型。spa
舉個例子,寫一個函數,函數接受一個參數,參數類型能夠是字符串或者數值,這個函數要返回參數的長度。按照ts的寫法,應該是這樣:code
const getLength = (target: string | number): number => { // string if (target.length || target.length === 0) { return target.length; // number } else { return target.toString().length; } }
但這樣寫有問題,由於target的類型限定了是string或number,因爲number類型的參數是沒有length屬性的,這樣ts就會看成這個參數是沒有length屬性的,這時,能夠強行把函數體裏的target類型寫成string類型,類型斷言能夠作到。對象
類型斷言的實現方式是在須要類型斷言的變量前面,使用<類型>的形式,或者使用as把變量指定爲某類型。blog
const getLength = (target: string | number): number => { // string if ((<string>target).length || (target as string).length === 0) { return (<string>target).length; // number } else { return target.toString().length; } } } console.log(getLength(1230000)); // 7 console.log(getLength('123')); // 3
類型斷言的缺點是須要在變量出現的每一個地方進行類型斷言,使用自定義類型保護能夠替換類型斷言的作法。
interface ModalWidth { width: number, unit: string, background?: string, readonly id: string } const getModalWidth = ({ width, unit }: ModalWidth): string => { return width + unit; } console.log(getModalWidth({ width: 100, unit: 'px', id: 'A0001' })); // "100px"
當容許變量(這裏指對象)中的屬性多於接口(對象形式的接口)中定義的屬性時,有三種方法解決接口屬性的校驗問題,類型斷言、類型兼容性、索引簽名[prop: string]: any。
console.log(getModalWidth({ width: 100, unit: 'px', id: 'A0001', height: 200 } as ModalWidth)); // as類型斷言 // "100px"
所謂的類型兼容性,是指把對象字面量改寫成先用一個變量存儲含有數據的對象,再把這個變量做爲參數傳到函數裏。
舉個例子,變量b存儲了a對象,a中的屬性能夠多於b的,也就是傳給函數func(b)的參數對象b中的屬性只能多,不能少。
const b = a; func(b);
因此
const modalWidth = { width: 100, unit: 'px', id: 'A0001', height: 200 } console.log(getModalWidth(modalWidth)); // "100px"
interface ModalWidth { width: number, unit: string, background?: string, readonly id: string, [propname: string]: any } console.log(getModalWidth({ width: 100, unit: 'px', id: 'A0001', height: 200 })); // "100px"
interface widthSize { 0: number, readonly 1: string } const widthList: widthSize = [100, 'px']; console.log(widthList); // [100, "px"]
interface addXY { (x: number, y: number): number } const XAndY: addXY = (x, y) => { return x + y; } console.log(XAndY(1, 1)); // 2 // 等價於類型別名的定義形式 type AddXY2 = (x: number, y: number) => number const XAndY: AddXY2 = (x, y) => { return x + y; } console.log(XAndY(1, 1)); // 2
// 數字索引 interface Shoes { [id: number]: string } const shoes1: Shoes = { 17: 'AN000' } console.log(shoes1); // {17: "AN000"}
// 字符串索引 interface Shoes { [s: string]: number } const shoes1: Shoes = { 'size': 3 } console.log(shoes1); // {size: 3}
函數做爲對象,也會擁有屬性。
舉個例子。
定義一個混合類型的接口,接口中有函數,有屬性。
interface Mixture { (): void, type: string }
用變量getMixture存儲一個函數,這個函數有點特殊,返回值類型是剛纔定義的接口Mixture。在這個函數體內,以Mixture定義的形式,再寫一個函數mixture,mixture沒有返回值,但有一個type屬性,最終返回mixture。
const getMixture = (): Mixture => { const mixture = () => { console.log(mixture.type); } mixture.type = 'mix_type'; return mixture; }
這樣一來,getMixture就是一個返回Mixture類型的函數,一個函數返回一個函數,這就像閉包同樣,最後調用方法執行一下,會輸出mixture.type的值。
const getType: Mixture = getMixture(); getType(); // "mix_type"
interface Color { color: string } interface Sphere extends Color{ radius: number } interface Cube extends Color { width: number } const sphere: Sphere = { color: '#fff', radius: 10 } const cube: Cube = { color: '#0f0', width: 10 } console.log(sphere); console.log(cube); /** * {color: "#fff", radius: 10} * {color: "#0f0", width: 10} */
const countArgs = (x: number, y: number, z?: number): number => x + y + (z ? z : 0); console.log(countArgs(1, 2, 3)); // 6
const countArgs = (unit?: any, ..args: number[]) => { let totals = 0; args.forEach((item: number) => { totals += item }); return totals + unit; }; console.log(countArgs(1, 2, 3, 9)); // "14px" console.log(countArgs(1, 5, 9)); // 15
// 默認參數 let add4 = (x: number, y: number = 0) => x + y; console.log(add4(3, 7)); // 10
函數重載指的是函數名相同,根據傳入的參數的不一樣,決定不一樣的操做。
函數重載舉例。
function getWidth(str: string): string; function getWidth(num: number): number; function getWidth(arg: any): any { if (typeof arg === 'string') { return arg; } else { return arg + 'px'; } } console.log(getWidth(15)); // "15px" console.log(getWidth('17px')); // "17px"
一個泛型
const colorList = <T>(a: number = 0, b: T): void => { a = a + Math.ceil(Math.random() * 10); console.log(`${ a }_${ b }`); } console.log(colorList<string>(90, 'min')); // "78_min"
多個泛型
const colorList = <T, Q>(a: number = 0, b: T, c: Q): void => { a = a + Math.ceil(Math.random() * 10); console.log(`${ a }_${ b }_${ c }`); } console.log(colorList<string, number>(90, 'min', 0)); // "94_min_0"
type ColorList = <T>(a: number, b: T) => void; let colorArr: ColorList = (a, b) => { console.log(`${ a }_${ b }`); } colorArr(10, 'px'); // "10_px"
interface getType { <T>(type: T, num: number): T[] } // 能夠把泛型提到外面,這樣接口裏面的屬性和方法均可以用 interface getType<T> { (type: T, num: number): T[], _name: T }
// 泛型約束,泛型須要知足必定的條件 interface ValueWithLength { length: number }; // 這裏的泛型約束是T含有一個length屬性 const getArray6 = <T extends ValueWithLength>(arg: T, times: number = 3): T[] => { return new Array(times).fill(arg); } console.log(getArray6([1, 2])); // [[1, 2], [1, 2], [1, 2]] console.log(getArray6({ length: 2 })); // [{ length: 2 }, { length: 2 }, { length: 2 }] console.log(getArray6('abc')); // ["abc", "abc", "abc"]
// 在泛型約束中使用類型參數 const getArray7 = <T, K extends keyof T>(obj: T, propname: K) => { return obj[propname]; } const obj1 = { name: 'Tom', age: 18 } console.log(getArray7(obj1, 'age')); // 18
// readonly class UserInfo1 { readonly name: string; constructor(name: string) { this.name = name; } } var user1 = new UserInfo1('Tom'); // user1.name = 'Jerry'; // 修改只讀屬性會報錯
// 修飾符屬性的簡寫 // 在constructor函數參數前面加修飾符,既能夠修飾屬性,同時也把屬性放到實例上 class A { constructor(public age: number) {} } var a1 = new A(19); console.log(a1); // 會看到類中含有屬性age
靜態方法會被類自己調用,但不能被子類繼承,使用static標記靜態方法。
在普通方法中,super對象指向父類的原型對象,在靜態方法中,super對象指向父類。
// 靜態屬性 class Parent6 { public static age: number = 17; public static getAge () { return this.age } // constructor(age: number) { // this.age = age; // 這裏會報錯,由於age是靜態屬性 // } } var p6 = new Parent6(); // console.log(p6.age); // error console.log(Parent6.age); // 靜態屬性容許類自己訪問 // 17 console.log(Parent6.getAge()); // 靜態方法容許類自己調用 // 17
private屬性不能被類自己訪問,只能夠類內部訪問。
class Parent7 { private static age: number = 17; public static getAge (age: number) { return this.age; } } var p7 = new Parent7(); // console.log(Parent7.age); // 報錯,age是private
// 可選屬性 class Parent8 { constructor(public name: string, public age?: number, public sex?: string) {} } // 可選屬性沒有傳值的話,值爲undefined var p81 = new Parent8('Tom'); console.log(p81); // Parent8 {name: "Tom", age: undefined, sex: undefined} var p82 = new Parent8('Tom', 18); console.log(p82); // Parent8 {name: "Tom", age: 18, sex: undefined} var p83 = new Parent8('Tom', 18, 'male'); console.log(p83); // Parent8 {name: "Tom", age: 18, sex: "male"}
class Parent9 { constructor(public name: string, public age?: number, public sex?: string) {}; get nameInfo () { return `${ this.name },${ this.age }` } set nameInfo (newval) { console.log('setter: ' + newval); this.name = newval; } } var p91 = new Parent9('Tom', 19, 'man'); console.log(p91.nameInfo); // "Tom,19" p91.nameInfo = 'Jack'; // "setter: Jack" console.log(p91.nameInfo); // "Jack,19"
// 抽象類 // 抽象類沒法實例化,但能被繼承 abstract class Parent10 { constructor(public name: string) {}; public abstract printName (): void } class Child10 extends Parent10 { constructor(name: string) { super(name); this.name = name; } public printName () { console.log(this.name); } } const c10 = new Child10('Tom'); console.log(c10); // Child10 {name: "Tom"} c10.printName(); // "Tom"
//抽象類與存取器 abstract class Parent11 { abstract _name: string abstract get insideName (): string abstract set insideName (value: string) } class Child11 extends Parent11 { public _name: string = 'Jack'; public set insideName (newval: string) { this._name = newval } public get insideName () { return this._name; } } var p2 = new Child11(); console.log(p2._name); // "Jack" p2.insideName = 'Jerry'; console.log(p2.insideName); // "Jerry"
// 實例的構造函數 class Parent12 { constructor(public name: string) {} } // const p12: Parent12 = new Parent12('Tom'); let p12 = new Parent12('Tom'); class Animals { constructor(public name: string) {} } p12 = new Animals('elephent'); console.log(p12 instanceof Parent12); // false console.log(p12 instanceof Animals); // true
對於類類型接口,接口檢測的是使用該接口定義的類建立的實例,省去部分定語,一句話歸納,接口檢測的是類的實例。
// 類繼承接口 interface FoodInterface { type: string } class FoodsClass implements FoodInterface { public static type: string }
以上這個例子,type屬性是靜態屬性,由類自己訪問,可是這個類繼承了接口,接口檢測的是實例,這裏使用了static已經代表實例上沒有這個type屬性,因此會報錯。
class Parent { protected name: string } interface Inter2 extends Parent {} class Child2 implements Inter2 { public name: string // 受保護的屬性只能在子類或者類內部訪問,而Child2沒有繼承Parent,因此會報錯 } // 這樣寫不報錯 class Child2 extends Parent implements Inter2 { name: string; // 須要關掉strict }
// 類類型使用泛型 const create = <T>(c: new() => T): T => { // new() => T 表示建立一個類 return new c(); } class Info7 { age: number; constructor() { this.age = 18; } } console.log(create<Info7>(Info7).age); // 18
enum Status { Uploading, Success, Failed }; console.log(Status.Uploading, Status.Success, Status.Failed); // 0 1 2 console.log(Status['Uploading'], Status['Success'], Status['Failed']); // 0 1 2
const UPLOADING = 1; enum Status { Uploading, Success = UPLOADING, // 這種方式,後面的屬性也要自定義值 Failed = 10 }; console.log(Status.Uploading, Status.Success, Status.Failed); // 0 1 10 console.log(Status['Uploading'], Status['Success'], Status['Failed']); // 0 1 10
enum Status2 { Uploading, Success = 9, Failed } console.log(Status2[9]); // Success
enum Status3 { Error = 'It\'s no data', Uploading = 'uploading...', Success = 'success', Failed = Error } console.log(Status3.Failed); // "It's no data"
異構枚舉既含有數字枚舉,還含有字符串枚舉。
enum Status4 { Status = 1, Message = 'Success' } console.log(Status4); console.log(Status4[1]); // status console.log(Status4['Status']); // 1 console.log(Status4['Message']); // Success
枚舉成員定義方式
enum E { A } enum E { A = '1' } enum E { A = 1, B = -2 }
// 枚舉成員類型 enum Animals9 { Dog = 1, Cat = 2 } interface Dog { type: Animals9.Dog } const dog: Dog = { type: 1 } console.log(dog); // { type: 1 }
enum Status { Off, On } interface Light { type: Status } const light: Light = { // type: 0, type: Status.Off, // type: Animals.Dog // 報錯 } console.log(light); // { type: 0 }
// const enum // 這樣寫能夠作到編譯後,直接把值賦給變量,而不是對象屬性的賦值 const enum Status5 { Success = 1 } enum Status6 { Success = 1 } // 值是同樣的,但背後但編譯結果是不同的 console.log(Status5.Success); // 1 // 編譯結果直接是一個數值 console.log(Status6.Success); // 1 // 編譯結果是一個對象屬性值