TypeScript__第三章

166 阅读5分钟

1. 你觉得使用ts的好处是什么?

1.1 TypeScriptJavaScript的加强版,它给JavaScript添加了可选的静态类型
和基于类的面向对象编程,它拓展了JavaScript的语法,ts的功能比js只多不少。
1.2 Typescript 是纯面向对象的编程语言,包含类和接口的概念。
1.3 TS 在开发时就能给出编译错误, 而 JS 错误则需要在运行时才能暴露。
1.4 作为强类型语言,你可以明确知道数据的类型。代码可读性极强,几乎每个人
都能理解。
1.5 ts中有很多很方便的特性, 比如(天生支持)可选链option chain。

const obj = response;
if(obj & obj.aa & obj.aa.bb) {
    const value = obj.aa.bb;
}
//根本不需要写那么长的判断
if(obj?.aa?.bb) {
    const value = obj.aa.bb;
}

2. type 和 interface的异同

重点:用interface描述数据结构,用type描述类型

2.1 都可以描述一个对象或者函数

interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void;
}

type User = {
  name: string
  age: number
};

type SetUser = (name: string, age: number)=> void;

2.2 都允许拓展(extends)

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。

// interface extends interface
interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

// type extends type
type Name = { 
  name: string; 
}
type User = Name & { age: number  };

// interface extends type
type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

// type extends interface
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

2.3 只有type可以做的

type 可以声明基本类型别名,联合类型,元组等类型

// 基本类型别名
type Name = string

// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div

3. 如何基于一个已有类型, 扩展出一个大部分内容相似, 但是有部分区别的类型?

3.1 首先可以通过 Pick 和 Omit

interface Test {
    name: string;
    sex: number;
    height: string;
}

type Sex = Pick<Test, 'sex'>;

const a: Sex = { sex: 1 };

type WithoutSex = Omit<Test, 'sex'>;

const b: WithoutSex = { name: '1111', height: 'sss' };

3.2 比如Partial, Required.

3.3 再者可以通过泛型.

4. 什么是泛型, 泛型的具体使用?

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。

可以把泛型理解为代表类型的参数

interface Test<T = any> {
    userId: T;
}

type TestA = Test<string>;
type TestB = Test<number>;

const a: TestA = {
    userId: '111',
};

const b: TestB = {
    userId: 2222,
};

5. 什么是装饰器

装饰函数的装饰器,大概率用来基于原有的函数,在不修改使用的情况下,
通过装饰器的写法,去修改原有函数的逻辑

6. 写一个计算函数执行时间的装饰器

// decorator/index.ts文件
export function before(beforeFn: any) {
    return function(target: any, name: any, descriptor: any) {
        const oldValue = descriptor.value;

        descriptor.value = function() {
            beforeFn.apply(this, arguments);
            return oldValue.apply(this, arguments);
        };

        return descriptor;
    };
}

export function after(afterFn: any) {
    return function(target: any, name: any, descriptor: any) {
        const oldValue = descriptor.value;

        descriptor.value = function() {
            const ret = oldValue.apply(this, arguments);
            afterFn.apply(this, arguments);
            return ret;
        };

        return descriptor;
    };
}

//name函数名;descriptor之前的函数
export function measure(target: any, name: any, descriptor: any) {
    const oldValue = descriptor.value;

    descriptor.value = async function() {
        const start = Date.now();
        const ret = await oldValue.apply(this, arguments);
        console.log(`${name}执行耗时 ${Date.now() - start}ms`);
        return ret;
    };

    return descriptor;
}

使用

// Home.vue文件
<template>
  <div class="home" @click="toAboutPage">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
    <div>
        倒计时:{{timeDisplay}}
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '../components/HelloWorld.vue';
import { RouterHelper } from '../lib/routerHelper';
import { Countdown, CountdownEventName, fillZero } from '../lib/countdown';
import { RoutePath } from '../router';
import { measure } from '../decorator';

@Component({
  components: {
    HelloWorld,
  },
})
export default class Home extends Vue {
    public timeDisplay: string = '';

    @measure
    public toAboutPage() {
        RouterHelper.push(RoutePath.About, {  testName: '1111' });
    }

    public created() {
        const countdown = new Countdown(Date.now() + 60 * 60 * 1000, 10);
        countdown.on(CountdownEventName.RUNNING, (remainTimeData) => {
            const { hours, minutes, seconds, count} = remainTimeData;
            this.timeDisplay = [hours, minutes, seconds, count].map(fillZero).join(':');
        });

    }
}
</script>

7. 写一个缓存的装饰器

// async_cache.ts文件
//这里的缓存时存在内存当中,一刷新就没有了
const cacheMap = new Map();

export function EnableCache(target: any, name: string, descriptor: PropertyDescriptor) {
    const val = descriptor.value;
    descriptor.value = async function(...args: any) {
        const cacheKey = name + JSON.stringify(args);
        if (!cacheMap.get(cacheKey)) {
            const cacheValue = Promise.resolve(val.apply(this, args)).catch((_) => cacheMap.set(cacheKey, null));
            cacheMap.set(cacheKey, cacheValue);
        }
        return cacheMap.get(cacheKey);
    };
    return descriptor;
}

使用

    // 某个.ts类文件
    class Test{
        @EnableCache
        public getInfo() {
            return Axios.get("/info")
        }
    }
    //或者
    interface userInfo {
        name: string
    }
    Axios.get<userInfo>("/info").then(res => {
        //返回值就会被约束类型了
    })

8. 实现一个路由跳转 通过ts约束参数的routeHelper

// RouterHelper.ts文件
import { Dictionary } from 'vue-router/types/router';
import Router, { RoutePath } from '../router';

//Dictionary是vue提供的,此处代表 { string: string }
export type BaseRouteType = Dictionary<string>;

export interface IndexParam extends BaseRouteType {
    name: string;
}

export interface AboutPageParam extends BaseRouteType {
    testName: string;
}

export interface UserPageParam extends BaseRouteType {
    userId: string;
}

export interface ParamsMap {
    [RoutePath.Index]: IndexParam;
    [RoutePath.About]: AboutPageParam;
    [RoutePath.User]: UserPageParam;
}


export class RouterHelper {
    public static replace<T extends RoutePath>(routePath: T, params: ParamsMap[T]) {
        Router.replace({
            path: routePath,
            query: params,
        });
    }

    public static push<T extends RoutePath>(routePath: T, params: ParamsMap[T]) {
        Router.push({
            path: routePath,
            query: params,
        });
    }
}

// router/index.ts文件,比如vue项目中的路由表
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import Home from '../views/Home.vue';

Vue.use(VueRouter);

export enum RoutePath {
    /** 首页 */
    Index = '/',
    /** 关于页面 */
    About = '/about',
    /** 用户页面 */
    User = '/user',
}

const routes: RouteConfig[] = [
  {
    path: RoutePath.Index,
    component: Home,
  },
  {
    path: RoutePath.About,
    component: () => import('../views/About.vue'),
  },
  {
    path: RoutePath.User,
    component: () => import('../views/User.vue'),
  },
];

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
});

export default router;

使用

// Home.vue
<template>
  <div class="home" @click="toAboutPage">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
    <div>
        倒计时:{{timeDisplay}}
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '../components/HelloWorld.vue';
import { RouterHelper } from '../lib/routerHelper';
import { Countdown, CountdownEventName, fillZero } from '../lib/countdown';
import { RoutePath } from '../router';
import { measure } from '../decorator';

@Component({
  components: {
    HelloWorld,
  },
})
export default class Home extends Vue {
    public timeDisplay: string = '';

    @measure
    public toAboutPage() {
    //因为有了约束,在开发的过程中写漏了会有报错提示
        RouterHelper.push(RoutePath.About, { testName: '1111' });
        RouterHelper.push(RoutePath.Index, { testName: '1111', name: "6" });
        RouterHelper.push(RoutePath.User, { userId: '1111' });
    }

    public created() {
        const countdown = new Countdown(Date.now() + 60 * 60 * 1000, 10);
        countdown.on(CountdownEventName.RUNNING, (remainTimeData) => {
            const { hours, minutes, seconds, count} = remainTimeData;
            this.timeDisplay = [hours, minutes, seconds, count].map(fillZero).join(':');
        });

    }
}
</script>

9. 实现一个基于ts和事件模式的countdown基础类

// Countdown.ts
//基于事件发布订阅模式,所以利用到EventEmitter,EventEmitter是同步的
import { EventEmitter } from 'eventemitter3';

export interface RemainTimeData {
    /** 天数 */
    days: number;
    /**
     * 小时数
     */
    hours: number;
    /**
     * 分钟数
     */
    minutes: number;
    /**
     * 秒数
     */
    seconds: number;
    /**
     * 毫秒数
     */
    count: number;
}

export type CountdownCallback = (remainTimeData: RemainTimeData, remainTime: number) => void;

enum CountdownStatus {
    running,
    paused,
    stoped,
}

export enum CountdownEventName {
    START = 'start',
    STOP = 'stop',
    RUNNING = 'running',
}

interface CountdownEventMap {
    [CountdownEventName.START]: [];
    [CountdownEventName.STOP]: [];
    [CountdownEventName.RUNNING]: [RemainTimeData, number];
}

export function fillZero(num: number) {
    return `0${num}`.slice(-2);
}

export class Countdown extends EventEmitter<CountdownEventMap> {
    private static COUNT_IN_MILLISECOND: number = 1 * 100;
    private static SECOND_IN_MILLISECOND: number = 10 * Countdown.COUNT_IN_MILLISECOND;
    private static MINUTE_IN_MILLISECOND: number = 60 * Countdown.SECOND_IN_MILLISECOND;
    private static HOUR_IN_MILLISECOND: number = 60 * Countdown.MINUTE_IN_MILLISECOND;
    private static DAY_IN_MILLISECOND: number = 24 * Countdown.HOUR_IN_MILLISECOND;

    private endTime: number;
    private remainTime: number = 0;
    private status: CountdownStatus = CountdownStatus.stoped;
    private step: number;

    constructor(endTime: number, step: number = 1e3) {
        super();

        this.endTime = endTime;
        this.step = step;

        this.start();
    }

    public start() {
        this.emit(CountdownEventName.START);

        this.status = CountdownStatus.running;
        this.countdown();
    }

    public stop() {
        this.emit(CountdownEventName.STOP);

        this.status = CountdownStatus.stoped;
    }

    private countdown() {
        if (this.status !== CountdownStatus.running) {
            return;
        }

        this.remainTime = Math.max(this.endTime - Date.now(), 0);

        this.emit(CountdownEventName.RUNNING, this.parseRemainTime(this.remainTime), this.remainTime);

        if (this.remainTime > 0) {
            setTimeout(() => this.countdown(), this.step);
        } else {
            this.stop();
        }
    }

    private parseRemainTime(remainTime: number): RemainTimeData {
        let time = remainTime;

        const days = Math.floor(time / Countdown.DAY_IN_MILLISECOND);
        time = time % Countdown.DAY_IN_MILLISECOND;

        const hours = Math.floor(time / Countdown.HOUR_IN_MILLISECOND);
        time = time % Countdown.HOUR_IN_MILLISECOND;

        const minutes = Math.floor(time / Countdown.MINUTE_IN_MILLISECOND);
        time = time % Countdown.MINUTE_IN_MILLISECOND;

        const seconds = Math.floor(time / Countdown.SECOND_IN_MILLISECOND);
        time = time % Countdown.SECOND_IN_MILLISECOND;

        const count = Math.floor(time / Countdown.COUNT_IN_MILLISECOND);

        return {
            days,
            hours,
            minutes,
            seconds,
            count,
        };
    }
}

使用

// Home.vue
<template>
  <div class="home" @click="toAboutPage">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
    <div>
        倒计时:{{timeDisplay}}
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '../components/HelloWorld.vue';
import { RouterHelper } from '../lib/routerHelper';
import { Countdown, CountdownEventName, fillZero } from '../lib/countdown';
import { RoutePath } from '../router';
import { measure } from '../decorator';

@Component({
  components: {
    HelloWorld,
  },
})
export default class Home extends Vue {
    public timeDisplay: string = '';

    @measure
    public toAboutPage() {
    //因为有了约束,在开发的过程中写漏了会有报错提示
        RouterHelper.push(RoutePath.About, { testName: '1111' });
        RouterHelper.push(RoutePath.Index, { testName: '1111', name: "6" });
        RouterHelper.push(RoutePath.User, { userId: '1111' });
    }

    public created() {
        const countdown = new Countdown(Date.now() + 60 * 60 * 1000, 10);
        countdown.on(CountdownEventName.RUNNING, (remainTimeData) => {
            const { hours, minutes, seconds, count} = remainTimeData;
            this.timeDisplay = [hours, minutes, seconds, count].map(fillZero).join(':');
        });

    }
}
</script>

10、面试题:异构类型每一项的枚举值 => js本质实现(手写一个异构枚举的实现)

let Enum;
(function (Enum) {
    // 正向
    Enum["A"] = 0;
    Enum["B"] = 1;
    Enum["C"] = "C";
    Enum["D"] = "D";
    Enum["E"] = 8;
    Enum["F"] = 9;

    // 逆向
    Enum[0] = "A";
    Enum[1] = "B";
    Enum[8] = "E";
    Enum[9] = "F";
})(Enum || (Enum = {}))