1. 你觉得使用ts的好处是什么?
1.1 TypeScript是JavaScript的加强版,它给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 = {}))