写在开头
本次
TypeScript一共有15道题,由易道难,可以说是涵盖了所有的使用场景,入门容易,精通难
之前的上集有8道题,没有看过的小伙伴,可以看这之前的文章:
最近技术团队会马上入驻公众号,后期原创文章会不断增多,广告也有,但是大家请理解,这也是创作的动力,大部分收入是会用来发福利,上次就发了一百本书.有的广告还是可以白嫖的课程,点击进去看看也挺好
正式开始
第八题,模拟动态返回数据,我使用的是泛型解题,此时不是最优解法,
AdminsApiResponse和
DatesApiResponse可以进一步封装抽象成一个接口.有兴趣的可以继续优化
interface User { type : 'user' ; name: string; age: number; occupation: string; } interface Admin { type : 'admin' ; name: string; age: number; role: string; }type responseData = Date;type Person = User | Admin; const admins: Admin[] = [ { type : 'admin' , name: 'Jane Doe' , age: 32, role: 'Administrator' }, { type : 'admin' , name: 'Bruce Willis' , age: 64, role: 'World saver' }, ]; const users: User[] = [ { type : 'user' , name: 'Max Mustermann' , age: 25, occupation: 'Chimney sweep' , }, { type : 'user' , name: 'Kate Müller' , age: 23, occupation: 'Astronaut' }, ];type AdminsApiResponse<T> = | { status: 'success' ; data: T[]; } | { status: 'error' ; error: string; };type DatesApiResponse<T> = | { status: 'success' ; data: T; } | { status: 'error' ; error: string; };function requestAdmins(callback: (response: AdminsApiResponse<Admin>) => void) { callback({ status: 'success' , data: admins, }); }function requestUsers(callback: (response: AdminsApiResponse<User>) => void) { callback({ status: 'success' , data: users, }); }function requestCurrentServerTime( callback: (response: DatesApiResponse<number>) => void ) { callback({ status: 'success' , data: Date.now(), }); }function requestCoffeeMachineQueueLength( callback: (response: AdminsApiResponse<User>) => void ) { callback({ status: 'error' , error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.' , }); }function logPerson(person: Person) { console.log( ` - ${chalk.green(person.name)} , ${person.age} , ${ person.type === 'admin' ? person.role : person.occupation }` ); }function startTheApp(callback: (error: Error | null) => void) { requestAdmins((adminsResponse) => { console.log(chalk.yellow('Admins:' )); if (adminsResponse.status === 'success' ) { adminsResponse.data.forEach(logPerson); } else { return callback(new Error(adminsResponse.error)); } console.log(); requestUsers((usersResponse) => { console.log(chalk.yellow('Users:' )); if (usersResponse.status === 'success' ) { usersResponse.data.forEach(logPerson); } else { return callback(new Error(usersResponse.error)); } console.log(); requestCurrentServerTime((serverTimeResponse) => { console.log(chalk.yellow('Server time:' )); if (serverTimeResponse.status === 'success' ) { console.log( ` ${new Date(serverTimeResponse.data).toLocaleString()} ` ); } else { return callback(new Error(serverTimeResponse.error)); } console.log(); requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => { console.log(chalk.yellow('Coffee machine queue length:' )); if (coffeeMachineQueueLengthResponse.status === 'success' ) { console.log(` ${coffeeMachineQueueLengthResponse.data} `); } else { return callback(new Error(coffeeMachineQueueLengthResponse.error)); } callback(null); }); }); }); }); } startTheApp((e: Error | null) => { console.log(); if (e) { console.log( `Error: "${e.message} " , but it's fine, sometimes errors are inevitable.` ); } else { console.log(' Success!'); } });
第九题,还是考察泛型,
promisify的编写,传入一个返回
promise的函数,函数接受一个参数,这个参数是一个函数,它有对应的根据泛型生成的参数,返回值为
void,同样这个函数的参数也为函数,返回值也为
void
interface User { type : 'user' ; name: string; age: number; occupation: string; } interface Admin { type : 'admin' ; name: string; age: number; role: string; }type Person = User | Admin; const admins: Admin[] = [ { type : 'admin' , name: 'Jane Doe' , age: 32, role: 'Administrator' }, { type : 'admin' , name: 'Bruce Willis' , age: 64, role: 'World saver' } ]; const users: User[] = [ { type : 'user' , name: 'Max Mustermann' , age: 25, occupation: 'Chimney sweep' }, { type : 'user' , name: 'Kate Müller' , age: 23, occupation: 'Astronaut' } ];type ApiResponse<T> = ( { status: 'success' ; data: T; } | { status: 'error' ; error: string; } );function promisify<T>(fn: (callback: (arg: ApiResponse<T>) => void) => void): () => Promise<T> function promisify(arg: unknown): unknown { return null; } const oldApi = { requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) { callback({ status: 'success' , data: admins }); }, requestUsers(callback: (response: ApiResponse<User[]>) => void) { callback({ status: 'success' , data: users }); }, requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) { callback({ status: 'success' , data: Date.now() }); }, requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) { callback({ status: 'error' , error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.' }); } }; const api = { requestAdmins: promisify(oldApi.requestAdmins), requestUsers: promisify(oldApi.requestUsers), requestCurrentServerTime: promisify(oldApi.requestCurrentServerTime), requestCoffeeMachineQueueLength: promisify(oldApi.requestCoffeeMachineQueueLength) };function logPerson(person: Person) { console.log( ` - ${chalk.green(person.name)} , ${person.age} , ${person.type === 'admin' ? person.role : person.occupation} ` ); } async function startTheApp () { console.log(chalk.yellow('Admins:' )); (await api.requestAdmins()).forEach(logPerson); console.log(); console.log(chalk.yellow('Users:' )); (await api.requestUsers()).forEach(logPerson); console.log(); console.log(chalk.yellow('Server time:' )); console.log(` ${new Date(await api.requestCurrentServerTime()).toLocaleString()} `); console.log(); console.log(chalk.yellow('Coffee machine queue length:' )); console.log(` ${await api.requestCoffeeMachineQueueLength()} `); } startTheApp().then ( () => { console.log('Success!' ); }, (e: Error) => { console.log(`Error: "${e.message} " , but it's fine, sometimes errors are inevitable.`); } );
declare module 'str-utils' { export function strReverse(arg:string): string; export function strToLower(arg:string): string; export function strToUpper(arg:string): string; export function strRandomize(arg:string): string; export function strInvertCase(arg:string): string; } import { strReverse, strToLower, strToUpper, strRandomize, strInvertCase } from 'str-utils' ; interface User { type : 'user' ; name: string; age: number; occupation: string; } interface Admin { type : 'admin' ; name: string; age: number; role: string; }type Person = User | Admin; const admins: Admin[] = [ { type : 'admin' , name: 'Jane Doe' , age: 32, role: 'Administrator' }, { type : 'admin' , name: 'Bruce Willis' , age: 64, role: 'World saver' }, { type : 'admin' , name: 'Steve' , age: 40, role: 'Steve' }, { type : 'admin' , name: 'Will Bruces' , age: 30, role: 'Overseer' }, { type : 'admin' , name: 'Superwoman' , age: 28, role: 'Customer support' } ]; const users: User[] = [ { type : 'user' , name: 'Max Mustermann' , age: 25, occupation: 'Chimney sweep' }, { type : 'user' , name: 'Kate Müller' , age: 23, occupation: 'Astronaut' }, { type : 'user' , name: 'Moses' , age: 70, occupation: 'Desert guide' }, { type : 'user' , name: 'Superman' , age: 28, occupation: 'Ordinary person' }, { type : 'user' , name: 'Inspector Gadget' , age: 31, occupation: 'Undercover' } ]; const isAdmin = (person: Person): person is Admin => person.type === 'admin' ; const isUser = (person: Person): person is User => person.type === 'user' ; const nameDecorators = [ strReverse, strToLower, strToUpper, strRandomize, strInvertCase ];function logPerson(person: Person) { let additionalInformation: string = '' ; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } const randomNameDecorator = nameDecorators[ Math.round(Math.random() * (nameDecorators.length - 1)) ]; const name = randomNameDecorator(person.name); console.log( ` - ${chalk.green(name)} , ${person.age} , ${additionalInformation} ` ); } ([] as Person[]) .concat(users, admins) .forEach(logPerson);
第十一题,我使用了泛型和
declare module解题,如果你有更优解法,可以跟我聊聊,自认为这题没毛病.上面那道题我说两个
interface可以进一步优化,也可以参考这个思路
type func = <T>(input: T[], comparator: (a: T, b: T) => number) => T | null;type index<L> = <T>(input: T[], comparator: (a: T, b: T) => number) => L;declare module 'stats' { export const getMaxIndex: index<number>; export const getMaxElement: func; export const getMinIndex: index<number>; export const getMinElement: func; export const getMedianIndex: index<number>; export const getMedianElement: func; export const getAverageValue: func; } import { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue } from 'stats' ; interface User { type : 'user' ; name: string; age: number; occupation: string; } interface Admin { type : 'admin' ; name: string; age: number; role: string; } const admins: Admin[] = [ { type : 'admin' , name: 'Jane Doe' , age: 32, role: 'Administrator' }, { type : 'admin' , name: 'Bruce Willis' , age: 64, role: 'World saver' }, { type : 'admin' , name: 'Steve' , age: 40, role: 'Steve' }, { type : 'admin' , name: 'Will Bruces' , age: 30, role: 'Overseer' }, { type : 'admin' , name: 'Superwoman' , age: 28, role: 'Customer support' } ]; const users: User[] = [ { type : 'user' , name: 'Max Mustermann' , age: 25, occupation: 'Chimney sweep' }, { type : 'user' , name: 'Kate Müller' , age: 23, occupation: 'Astronaut' }, { type : 'user' , name: 'Moses' , age: 70, occupation: 'Desert guide' }, { type : 'user' , name: 'Superman' , age: 28, occupation: 'Ordinary person' }, { type : 'user' , name: 'Inspector Gadget' , age: 31, occupation: 'Undercover' } ];function logUser(user: User | null) { if (!user) { console.log(' - none' ); return ; } const pos = users.indexOf(user) + 1; console.log(` - #${pos} User: ${chalk.green(user.name)}, ${user.age}, ${user.occupation}`); }function logAdmin(admin: Admin | null) { if (!admin) { console.log(' - none' ); return ; } const pos = admins.indexOf(admin) + 1; console.log(` - #${pos} Admin: ${chalk.green(admin.name)}, ${admin.age}, ${admin.role}`); } const compareUsers = (a: User, b: User) => a.age - b.age; const compareAdmins = (a: Admin, b: Admin) => a.age - b.age; const colorizeIndex = (value: number) => chalk.red(String(value + 1)); console.log(chalk.yellow('Youngest user:' )); logUser(getMinElement(users, compareUsers)); console.log(` - was ${colorizeIndex(getMinIndex(users, compareUsers))} th to register`); console.log(); console.log(chalk.yellow('Median user:' )); logUser(getMedianElement(users, compareUsers)); console.log(` - was ${colorizeIndex(getMedianIndex(users, compareUsers))} th to register`); console.log(); console.log(chalk.yellow('Oldest user:' )); logUser(getMaxElement(users, compareUsers)); console.log(` - was ${colorizeIndex(getMaxIndex(users, compareUsers))} th to register`); console.log(); console.log(chalk.yellow('Average user age:' )); console.log(` - ${chalk.red(String(getAverageValue(users, ({age} : User) => age)))} years`); console.log(); console.log(chalk.yellow('Youngest admin:' )); logAdmin(getMinElement(admins, compareAdmins)); console.log(` - was ${colorizeIndex(getMinIndex(users, compareUsers))} th to register`); console.log(); console.log(chalk.yellow('Median admin:' )); logAdmin(getMedianElement(admins, compareAdmins)); console.log(` - was ${colorizeIndex(getMedianIndex(users, compareUsers))} th to register`); console.log(); console.log(chalk.yellow('Oldest admin:' )); logAdmin(getMaxElement(admins, compareAdmins)); console.log(` - was ${colorizeIndex(getMaxIndex(users, compareUsers))} th to register`); console.log(); console.log(chalk.yellow('Average admin age:' )); console.log(` - ${chalk.red(String(getAverageValue(admins, ({age} : Admin) => age)))} years`);
import 'date-wizard' ;declare module 'date-wizard' { // Add your module extensions here. export function pad<T>(name: T): T; export function dateDetails<T>(name: T): { hours: number }; } //.. import * as dateWizard from 'date-wizard' ; import './module-augmentations/date-wizard' ; interface User { type : 'user' ; name: string; age: number; occupation: string; registered: Date; } interface Admin { type : 'admin' ; name: string; age: number; role: string; registered: Date; }type Person = User | Admin; const admins: Admin[] = [ { type : 'admin' , name: 'Jane Doe' , age: 32, role: 'Administrator' , registered: new Date('2016-06-01T16:23:13' ) }, { type : 'admin' , name: 'Bruce Willis' , age: 64, role: 'World saver' , registered: new Date('2017-02-11T12:12:11' ) }, { type : 'admin' , name: 'Steve' , age: 40, role: 'Steve' , registered: new Date('2018-01-05T11:02:30' ) }, { type : 'admin' , name: 'Will Bruces' , age: 30, role: 'Overseer' , registered: new Date('2018-08-12T10:01:24' ) }, { type : 'admin' , name: 'Superwoman' , age: 28, role: 'Customer support' , registered: new Date('2019-03-25T07:51:05' ) } ]; const users: User[] = [ { type : 'user' , name: 'Max Mustermann' , age: 25, occupation: 'Chimney sweep' , registered: new Date('2016-02-15T09:25:13' ) }, { type : 'user' , name: 'Kate Müller' , age: 23, occupation: 'Astronaut' , registered: new Date('2016-03-23T12:47:03' ) }, { type : 'user' , name: 'Moses' , age: 70, occupation: 'Desert guide' , registered: new Date('2017-02-19T17:22:56' ) }, { type : 'user' , name: 'Superman' , age: 28, occupation: 'Ordinary person' , registered: new Date('2018-02-25T19:44:28' ) }, { type : 'user' , name: 'Inspector Gadget' , age: 31, occupation: 'Undercover' , registered: new Date('2019-03-25T09:29:12' ) } ]; const isAdmin = (person: Person): person is Admin => person.type === 'admin' ; const isUser = (person: Person): person is User => person.type === 'user' ;function logPerson(person: Person, index: number) { let additionalInformation: string = '' ; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } let registeredAt = dateWizard(person.registered, '{date}.{month}.{year} {hours}:{minutes}' ); let num = `#${dateWizard.pad(index + 1)}`; console.log( ` - ${num} : ${chalk.green(person.name)} , ${person.age} , ${additionalInformation} , ${registeredAt} ` ); } console.log(chalk.yellow('All users:' )); ([] as Person[]) .concat(users, admins) .forEach(logPerson); console.log(); console.log(chalk.yellow('Early birds:' )); ([] as Person[]) .concat(users, admins) .filter((person) => dateWizard.dateDetails(person.registered).hours < 10) .forEach(logPerson);
第十三题,略有难度,考察联合类型、类型断言、泛型,由于数据库的查询,
find方法可能传入的参数是多种多样的,为了满足以下的
find使用场景,需要定义
Database这个类
interface User { _id: number; name: string; age: number; occupation: string; registered: string; } interface Admin { _id: number; name: string; age: number; role: string; registered: string; } async function testUsersDatabase () { const usersDatabase = new Database<User>(path.join(__dirname, 'users.txt' ), [ 'name' , 'occupation' , ]); // $eq operator means "===" , syntax {fieldName: {$gt : value}} // see more https://docs.mongodb.com/manual/reference/operator/query/eq/ expect( (await usersDatabase.find({ occupation: { $eq : 'Magical entity' } })).map( ({ _id }) => _id ) ).to.have.same.members([6, 8]); expect( ( await usersDatabase.find({ age: { $eq : 31 }, name: { $eq : 'Inspector Gadget' }, }) )[0]._id ).to.equal(5); // $gt operator means ">" , syntax {fieldName: {$gt : value}} // see more https://docs.mongodb.com/manual/reference/operator/query/gt/ expect( (await usersDatabase.find({ age: { $gt : 30 } })).map(({ _id }) => _id) ).to.have.same.members([3, 5, 6, 8]); // $lt operator means "<" , syntax {fieldName: {$lt : value}} // see more https://docs.mongodb.com/manual/reference/operator/query/lt/ expect( (await usersDatabase.find({ age: { $lt : 30 } })).map(({ _id }) => _id) ).to.have.same.members([0, 2, 4, 7]); // $and condition is satisfied when all the nested conditions are satisfied: {$and : [condition1, condition2, ...]} // see more https://docs.mongodb.com/manual/reference/operator/query/and/ // These examples return the same result: // usersDatabase.find({age: {$eq : 31}, name: {$eq : 'Inspector Gadget' }}); // usersDatabase.find({$and : [{age: {$eq : 31}}, {name: {$eq : 'Inspector Gadget' }}]}); expect( ( await usersDatabase.find({ $and : [{ age: { $gt : 30 } }, { age: { $lt : 40 } }], }) ).map(({ _id }) => _id) ).to.have.same.members([5, 6]); // $or condition is satisfied when at least one nested condition is satisfied: {$or : [condition1, condition2, ...]} // see more https://docs.mongodb.com/manual/reference/operator/query/or/ expect( ( await usersDatabase.find({ $or : [{ age: { $gt : 90 } }, { age: { $lt : 30 } }], }) ).map(({ _id }) => _id) ).to.have.same.members([0, 2, 4, 7, 8]); // $text operator means full text search. For simplicity this means finding words from the full-text search // fields which are specified in the Database constructor. No stemming or language processing other than // being case insensitive is not required. // Syntax {$text : 'Hello World' } - this return all records having both words in its full-text search fields. // It is also possible that queried words are spread among different full-text search fields. expect( (await usersDatabase.find({ $text : 'max' })).map(({ _id }) => _id) ).to.have.same.members([0, 7]); expect( (await usersDatabase.find({ $text : 'Hey' })).map(({ _id }) => _id) ).to.have.same.members([]); // $in operator checks if entry field value is within the specified list of accepted values. // Syntax {fieldName: {$in : [value1, value2, value3]}} // Equivalent to {$or : [{fieldName: {$eq : value1}}, {fieldName: {$eq : value2}}, {fieldName: {$eq : value3}}]} // see more https://docs.mongodb.com/manual/reference/operator/query/in / expect( (await usersDatabase.find({ _id: { $in : [0, 1, 2] } })).map( ({ _id }) => _id ) ).to.have.same.members([0, 2]); expect( (await usersDatabase.find({ age: { $in : [31, 99] } })).map(({ _id }) => _id) ).to.have.same.members([5, 8]); } async function testAdminsDatabase () { const adminsDatabase = new Database<Admin>( path.join(__dirname, 'admins.txt' ), ['name' , 'role' ] ); expect( (await adminsDatabase.find({ role: { $eq : 'Administrator' } })).map( ({ _id }) => _id ) ).to.have.same.members([0, 6]); expect( ( await adminsDatabase.find({ age: { $eq : 51 }, name: { $eq : 'Bill Gates' }, }) )[0]._id ).to.equal(7); expect( (await adminsDatabase.find({ age: { $gt : 30 } })).map(({ _id }) => _id) ).to.have.same.members([0, 2, 3, 6, 7, 8]); expect( (await adminsDatabase.find({ age: { $lt : 30 } })).map(({ _id }) => _id) ).to.have.same.members([5]); expect( ( await adminsDatabase.find({ $and : [{ age: { $gt : 30 } }, { age: { $lt : 40 } }], }) ).map(({ _id }) => _id) ).to.have.same.members([0, 8]); expect( ( await adminsDatabase.find({ $or : [{ age: { $lt : 30 } }, { age: { $gt : 60 } }], }) ).map(({ _id }) => _id) ).to.have.same.members([2, 5]); expect( (await adminsDatabase.find({ $text : 'WILL' })).map(({ _id }) => _id) ).to.have.same.members([4, 6]); expect( (await adminsDatabase.find({ $text : 'Administrator' })).map( ({ _id }) => _id ) ).to.have.same.members([0, 6]); expect( (await adminsDatabase.find({ $text : 'Br' })).map(({ _id }) => _id) ).to.have.same.members([]); expect( (await adminsDatabase.find({ _id: { $in : [0, 1, 2, 3] } })).map( ({ _id }) => _id ) ).to.have.same.members([0, 2, 3]); expect( (await adminsDatabase.find({ age: { $in : [30, 28] } })).map( ({ _id }) => _id ) ).to.have.same.members([4, 5]); } Promise.all([testUsersDatabase(), testAdminsDatabase()]).then ( () => console.log('All tests have succeeded, congratulations!' ), (e) => console.error(e.stack) ); //Database import * as fs from 'fs' ;export type FieldOp = | { $eq : string | number } | { $gt : string | number } | { $lt : string | number } | { $in : (string | number)[] };export type Query = | { $and : Query[] } | { $or : Query[] } | { $text : string } | ({ [field: string]: FieldOp } & { $and ?: never; $or ?: never; $text ?: never; });function matchOp(op: FieldOp, v: any) { if ('$eq' in op) { return v === op['$eq' ]; } else if ('$gt' in op) { return v > op['$gt' ]; } else if ('$lt' in op) { return v < op['$lt' ]; } else if ('$in' in op) { return op['$in' ].includes(v); } throw new Error(`Unrecognized op: ${op} `); }function matches(q: Query, r: unknown): boolean { if ('$and' in q) { return q.$and !.every((subq) => matches(subq, r)); } else if ('$or' in q) { return q.$or !.some((subq) => matches(subq, r)); } else if ('$text' in q) { const words = q.$text !.toLowerCase().split(' ' ); return words.every((w) => (r as any).$index [w]); } return Object.entries(q).every(([k, v]) => matchOp(v, (r as any)[k])); }export class Database<T> { protected filename: string; protected fullTextSearchFieldNames: string[]; protected records: T[]; constructor(filename: string, fullTextSearchFieldNames: string[]) { this.filename = filename; this.filename = filename; this.fullTextSearchFieldNames = fullTextSearchFieldNames; this.fullTextSearchFieldNames = fullTextSearchFieldNames; const text = fs.readFileSync(filename, 'utf8' ); const lines = text.split('\n' ); this.records = lines .filter((line) => line.startsWith('E' )) .map((line) => JSON.parse(line.slice(1))) .map((obj) => { obj.$index = {}; for (const f of fullTextSearchFieldNames) { const text = obj[f]; for (const word of text.split(' ' )) { obj.$index [word.toLowerCase()] = true ; } } return obj; }); } async find(query: Query): Promise<T[]> { return this.records.filter((r) => matches(query, r)); } }
写在最后
我是
Peter酱,前端架构师 擅长跨平台和极限场景性能优化 不定期会给你们推送高质量原创文章,公众号回复:
加群即可加入群聊
我把我的往期文章、源码都放在一个仓库了,手写
vue React webpack weboscket redis 静态资源服务器(实现
http缓存)
redux promise react-redux 微前端框架 ssr等,进阶前端专家不可或缺.
【
前端巅峰】即将迎来技术团队入驻,为打造更多前端技术专而奋斗.记得点个
关注、
在看。