TypeScript进阶(一)

此次在上次写的TypeScript基础语法基础上,做了一些补充,接着是语法的进阶。

补充

数组写法2

// 数组写法1
const arr2: (number | string)[] = [1, 2, 'abe', 3];

// 数组写法2
const arr1: Array = ['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

反向映射。

// 反向映射
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一般用来标记属性名的唯一性。

// 对象属性举例
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类型属性名的情况。

// 1、for in循环不出Symbol值属性名
const s5 = Symbol('name');

const obj3 = {
    age: 23,
    [s5]: 'Tom'
}

for (let  key  in  obj3) {
    console.log(key);
}
// age


// 2、Object.keys(obj3)也没法获取Symbol值属性名
console.log(Object.keys(obj3)); // ["age"]


// 3、Object.getOwnPropertyNames(obj3)也无法获取Symbol值属性名
console.log(Object.getOwnPropertyNames(obj3));  // ["age"]


// 4、JSON.stringify把一个对象转化为字符串,也无法获取Symbol 值属性名
console.log(JSON.stringify(obj3));  // '{"age":23}'

以下这些是可以获取到Symbol类型属性名的情况

// 1、Object.getOwnPropertySymbols(obj3)会返回对象中所有Symbol 值属性名
console.log(Object.getOwnPropertySymbols(obj3));    // [Symbol(name)]


// 2、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

类型断言

类型断言指的是强行把一个变量类型指定为所需要的类型。

举个例子,写一个函数,函数接受一个参数,参数类型可以是字符串或者数值,这个函数要返回参数的长度。按照ts的写法,应该是这样:

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把变量指定为某类型。

const getLength = (target: string | number): number => {
    
    // string
    if ((target).length || (target as string).length ===  0) {
    return (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 = (a: number =  0, b: T): void => {
    a = a + Math.ceil(Math.random() * 10);
    console.log(`${ a }_${ b }`);
}

console.log(colorList(90, 'min'));
// "78_min"

多个泛型

const colorList = (a: number = 0, b: T, c: Q): void => {
    a = a + Math.ceil(Math.random() * 10);
    console.log(`${ a }_${ b }_${ c }`);

}

console.log(colorList(90, 'min', 0));
// "94_min_0"

类型别名定义泛型

type ColorList = (a: number, b: T) => void;

let colorArr: ColorList = (a, b) => {
    console.log(`${ a }_${ b }`);
}

colorArr(10, 'px');
// "10_px"

接口中使用泛型

interface getType {
    (type: T, num: number): T[]
}

// 可以把泛型提到外面,这样接口里面的属性和方法都可以用
interface getType {
    (type: T, num: number): T[],
    _name: T
}

泛型约束

// 泛型约束,泛型需要满足一定的条件
interface ValueWithLength {
    length: number
};

// 这里的泛型约束是T含有一个length属性

const getArray6 = (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 = (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 = (c: new() => T): T => {  // new() => T 表示创建一个类
    return new c();
}

class Info7 {
    age:  number;
    constructor() {
        this.age = 18;
    }
}

console.log(create(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进阶(一)_第1张图片

你可能感兴趣的:(typescript)