TypeScript進階(一)

這次在上次寫的TypeScript基礎語法基礎上,作了一些補充,接着是語法的進階。數組

補充

數組寫法2

// 數組寫法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類型

// object
let obj = {
    name: 'Tom'
};

function getName(obj: object): void {
    console.log(obj);
}

getName(obj);
// { name: "Tom" }

Symbol類型

Symbol基礎

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做爲屬性名

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)]

Symbol的兩個靜態方法

Symbol.for
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.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對象指向父類的原型對象,在靜態方法中,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
// 這樣寫能夠作到編譯後,直接把值賦給變量,而不是對象屬性的賦值
const enum Status5 {
    Success = 1
}

enum Status6 {
    Success = 1
}

// 值是同樣的,但背後但編譯結果是不同的
console.log(Status5.Success);
// 1 
// 編譯結果直接是一個數值

console.log(Status6.Success);
// 1
// 編譯結果是一個對象屬性值

TypeScript進階(一).png

相關文章
相關標籤/搜索