ts面试题: 面试题1

1. 实现TS内置的ReturnType

// TS相关 实现TS内置的 ReturnType<T>
// 不能直接用原生TS的 ReturnType
const func = (a: number, b: number) => {
  if (a + b > 10)
    return a + b
  else
    return 'beyond_ten'
}

type a = MyReturnType<typeof func > // 推导出 number | 'beyond_ten'

答案

const func = (a: number, b: number) => {
    if (a + b > 10)
      return a + b
    else
      return 'beyond_ten'
  }
type MyReturnType<T> = T extends (...args) => infer Value ? Value : never

type a = MyReturnType<typeof func> // 推导出 number | 'beyond_ten'

2. 实现TS内置的 Pick<T, K>

用于从现有的类型中选择一个或多个属性来创建一个新的类型

// TS相关 实现TS内置的 Pick<T, K>
// 不能直接用原生TS的 Pick

interface UserInfo {
  name: string;
  password: string;
  phone: string;
}

type AdminUserInfo = MyPick<UserInfo, 'name' | 'password'>

const admin: AdminUserInfo = {
    name: 'Mick',
    password: '123456',
}

答案

interface UserInfo {
    name: string;
    password: string;
    phone: string;
  }

type myPick3<T, K extends keyof T> = {  
    [P in K]: T[P];  
};  
  
  type AdminUserInfo = myPick3<UserInfo, 'name' | 'password'>

3. 实现TS内置的 Exclude<T, U>

常用于处理联合类型

基本使用
1)  当 T 是联合类型,U 不是联合类型
type T = 'a' | 'b' | 'c';  
type U = 'b';  
  
type Result1 = Exclude<T, U>; // 结果是 'a' | 'c'  
   
let x: Result1;  
x = 'a'; // 正确  
x = 'b'; // 错误:'b' 不在 'a' | 'c' 类型中
2) 当 T 不是联合类型,U 是联合类型
type T = 'a';  
type U = 'b' | 'c';  
  
type Result2 = Exclude<T, U>; // 结果是 'a',因为 'a' 不能赋值给 'b' 或 'c'  
  
let y: Result2;  
y = 'a'; // 正确  
y = 'b'; // 错误:'b' 不在 'a' 类型中 

3) 当 T 和 U 都不是联合类型
type T = 'a';  
type U = 'b';  
  
type Result3 = Exclude<T, U>; // 结果是 'a',因为 'a' 不能赋值给 'b'  
    
let z: Result3;  
z = 'a'; // 正确  
z = 'b'; // 错误:'b' 不在 'a' 类型中
// TS相关 实现TS内置的 Exclude<T, U>
// 不能直接用原生TS的 Exclude
// 例如:
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

答案

type MyExclude<T,  U > = T extends U ? never : T


type Result2 = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

4. 实现一个Includes<T, U>类型处理

// TS相关 实现一个Includes<T, U>类型处理 
//  类似 JavaScript 的 Array.includes 方法,
//  接受两个参数,返回的类型要么是 true 要么是 false。

type MyType1 = Includes<['A', 'B', 'C', 'D'], 'A'> // expected to be `true`

type MyType2 = Includes<['A', 'B', 'C', 'D'], 'E'> // expected to be `false`

答案

type myIncludes<T extends any[], K> = T extends (infer U)[] ? (K extends U ? true : false) : false;
type test55 = myIncludes<['A', 'B', 'C', 'D'], 'A'>

5. 实现TS内置的 Readonly

// TS相关 实现TS内置的 Readonly<T>
// 不能直接用原生TS的 Readonly

interface UserInfo {
  name: string;
  phone: string;
}

const user: MyReadonly<UserInfo> = {
  name: 'Apple'
  phone: '12345678';
}

user.name = 'Banana' // 报错 Error: cannot reassign a readonly property

答案

// TS相关 实现TS内置的 Readonly<T>
// 不能直接用原生TS的 Readonly

interface UserInfo {
    name: string;
    phone: string;
  }
  

  type MyReadonly<T> = {
    readonly [P in keyof T]: T[P]
  }
  const user: MyReadonly<UserInfo> = {
    name: 'Apple',
    phone: '12345678'
  }
  
  user.name = 'Banana' // 报错 Error: cannot reassign a readonly property

6. 实现 LastItemType,接受一个数组T并返回其最后一个元素

// TS相关 实现 LastItemType<T>,接受一个数组T并返回其最后一个元素
type arr1 = [1, 2, 3]
type arr2 = ['a', 'b', 'c']

type last1 = LastItemType<arr1> // 表示类型为元素 3
type last2 = LastItemType<arr2> // 表示类型为元素 'c'

答案

type arr1 = [1, 2, 3]
type FirstElement<T extends any[]> =  
  T extends [...infer Rest, infer Last] ? Last : never;  
type test  = FirstElement<arr1>

7. 实现TS内置的 Omit<T, K>

用于从类型T排除一组属性K

// TS相关 实现TS内置的 Omit<T, K>
// 不能直接用原生TS的 Omit
interface UserInfo {
    name: string;
    password: string;
    phone: string;
  }

  type User = MyOmit<UserInfo, 'password' | 'phone'>
  
  const user: User = {
    name: 'Hello'
  }

答案

interface UserInfo {
    name: string;
    password: string;
    phone: string;
  }
  type MyOmit<T , U extends keyof T> = Pick<T, Exclude<keyof T, U>>

  type User = MyOmit<UserInfo, 'password' | 'phone'>

8. 元组转换为对象

将一个元组类型转换为对象类型,这个对象类型的键/值和元组中的元素对应。
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type result = TupleToObject<typeof tuple> // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

答案

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type TupleToObject<T extends readonly any[]> = {
    [P in T[number]]: P
}
type result = TupleToObject<typeof tuple> // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

9. 获取元组长度

创建一个Length泛型,这个泛型接受一个只读的元组,返回这个元组的长度。
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5

答案

type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type Length<T extends any[]> = T['length']

10. Awaited

假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

答案


11. IF Concat Push Unshift

实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T 
,以及一个判断为假时的返回类型 F。
 C 只能是 true 或者 false, T 和 F 可以是任意类型。
type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

// 在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,
// 返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
type Result = Concat<[1], [2]> // expected to be [1, 2]

//在类型系统里实现通用的 Array.push 。
type Result = Push<[1, 2], '3'> // [1, 2, '3']

// 实现类型版本的 Array.unshift。
type Result = Unshift<[1, 2], 0> // [0, 1, 2]

答案

// IF
 type If<R, A, B> = R extends true ? A : B
 type A = If<true, 'a', 'b'>  // expected to be 'a'

//Concat
type Concat<T extends any[], K extends any[]> = [...T, ...K]

// Push
// 在类型系统里实现通用的 Array.push
type Push<A extends any[], K> = [...A, K]
type Result = Push<[1, 2], '3'> // [1, 2, '3']

// unshift
type Unshift<A extends any[], K> = [K, ...A]

12. 实现内置的 Parameters 类型

const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]

答案

type MyParameters<F> = F extends (...arg: infer G) => infer R ? G : never

13. 对象部分属性只读

实现一个泛型MyReadonly2<T, K>,它带有两种类型的参数T和K。
类型 K 指定 T 中要被设置为只读 (readonly) 的属性。如果未提供K,
则应使所有属性都变为只读,就像普通的Readonly<T>一样。
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type test2 = MyReadonly2<Todo, "title" | "description">;

const todo: MyReadonly2<Todo, "title" | "description"> = {
  title: "Hey",
  description: "foobar",
  completed: false,
};

todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property
todo.completed = true; // OK

答案

type MyReadonly2<T, U extends keyof T> = {
  readonly [P in U]: T[P];
} & Pick<T, Exclude<keyof T, U>>;

14. 对象属性只读(递归)

// 实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。

// 您可以假设在此挑战中我们仅处理对象。
// 不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。

type X = { 
    x: { 
      a: 1
      b: 'hi'
    }
    y: 'hey'
  }
  
  type Expected = { 
    readonly x: { 
      readonly a: 1
      readonly b: 'hi'
    }
    readonly y: 'hey' 
  }
  
  type Todo = DeepReadonly<X> // should be same as `Expected`

答案

在这里插入代码片

15. 对象属性只读(递归)

实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。

您可以假设在此挑战中我们仅处理对象。不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
type X = { 
  x: { 
    a: 1
    b: 'hi'
  }
  y: 'hey'
}

type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'hi'
  }
  readonly y: 'hey' 
}

type Todo = DeepReadonly<X> // should be same as `Expected`

答案

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

16. 元组转联合类型

实现泛型TupleToUnion<T>,它返回元组所有值的合集。
type Arr = ['1', '2', '3']

type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'

答案

方式一:
type TupleToUnion<T extends any[]> = T extends (infer U)[] ? U : never
方式二:
type TupleToUnion<T extends any[]> = T extends any[] ? T[number] : never

17. 排除最后一项

实现一个泛型Pop<T>,它接受一个数组T,并返回一个由数组T的前 N-1 项(N 为数组T的长度)以相同的顺序组成的数组。
type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]

type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]

答案

type Pop<T> = T extends [...infer Other, infer last] ? Other : never

18. 可串联构造器 中等题

在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?

在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value)get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。

例如
declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// 期望 result 的类型是:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。

你可以假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。

答案

type Chainable<T extends object = {}> = {    
  option<K extends string, V>(key: K, value: V): Chainable<T & { [P in K]: V }>;    
  get(): T;    
};

19. 查找类型

有时,您可能希望根据某个属性在联合类型中查找类型。

在此挑战中,我们想通过在联合类型Cat | Dog中通过指定公共属性type的值来获取相应的类型。
换句话说,在以下示例中,LookUp<Dog | Cat, 'dog'>的结果应该是Dog,LookUp<Dog | Cat, 'cat'>
的结果应该是Cat。

  interface Cat {
    type: 'cat'
    breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
  }
  
  interface Dog {
    type: 'dog'
    breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
    color: 'brown' | 'white' | 'black'
  }

  type MyDog = LookUp<Cat| Dog, 'dog'> // expected to be `Dog`

答案

  type LookUp<T, K extends string, C = []> 
  = T extends Record<'type', K> ? T : never

20. 去除左侧空白

去除左侧空白
实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。

例如
type trimed = TrimLeft<'  Hello World  '> // 应推导出 'Hello World  '

答案

type TrimLeft<S> = S extends `${' '}${infer B}` ? TrimLeft<B> : S

21. 去除两端空白字符

实现Trim<T>,它接受一个明确的字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。

例如
type trimed = Trim<'  Hello World  '> // expected to be 'Hello World'

答案

type Trim<T extends string> = T extends `${' '}${infer B}${' '}` ? Trim<B> : T

22. 实现Capitalize 将第一个字符转成大写 中等

// Capitalize

// 实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。

// 例如
type capitalized = Capitalize2<'hello world'> // expected to be 'Hello world'

答案

type Capitalize2<S extends string> = S extends `${infer x}${infer tail}` ? `${Uppercase<x>}${tail}` : S;

23. Replace

实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。

例如
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'


答案

type Replace<S extends string, A extends string, B extends string> =
 S extends `${infer frond}${A}${infer end}` ? `${frond}${B}${end}` : never

24. ReplaceAll

ReplaceAll
实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。

例如
type replaced = ReplaceAll<' t y p e s ', ' ', ''> // 期望是 'types'

答案

type ReplaceAll<T extends string, K extends string, P  extends string> =
 T extends `${infer C}${K}${infer D}` ? ReplaceAll<`${C}${D}`, K, P> : T

25. Permutation联合类型转成数组全排列的联合类型 中等

// 实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

type perm = Permutation<'A' | 'B' | 'C'>; 
// ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']

答案

type Permutation<T, U = T> = [T] extends [never] // 检查 T 是否是 never 类型
  ? []
  : (T extends U //  检查 T 是否是 U 的一个成员.如果不是 返回空数组 []
    ? [T, ...Permutation<Exclude<U, T>>] // 它试图生成不包含当前元素 T 的所有其他元素的排列,并将这些排列与当前元素 T 组合起来
    : [])

26. 函数追加参数 中等

// 实现一个泛型 AppendArgument<Fn, A>,
// 对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。
// G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean> 

答案

type AppendArgument<T extends (...arg) => {}, B> =
 T extends (...arg: infer G) => infer R ?  (...x: [...G, B]) => R : never // “扩展”或“包裹”G这个元组

27. 实现内置的Record

// 1. 使用字符串字面量作为键
type PersonRecord = MyRecord<'name' | 'age', string>;  
  
const person: PersonRecord = {  
    name: 'Alice',  
    age: '30' // 注意:这里 age 实际上应该是 number 类型,但为了演示 Record 的使用,我们使用了 string  
}; 
// 2. 使用枚举作为键
enum Keys {  
    Name = 'name',  
    Age = 'age'  
}  

type PersonRecordWithEnum = MyRecord<Keys, string>;  
  
const personWithEnum: PersonRecordWithEnum = {  
    [Keys.Name]: 'Bob',  
    [Keys.Age]: '25'  
};
// 3. 使用数字作为键(不常见,但可能)
type NumberRecord = MyRecord<1 | 2 | 3, string>;  
  
const numbers: NumberRecord = {  
    1: 'one',  
    2: 'two',  
    3: 'three'  
};  
  
// 尝试使用非 1、2、3 的键会导致类型错误  
// const numbers: NumberRecord = { 1: 'one', 2: 'two', 4: 'four' }; // 错误
// 4. 使用泛型
type KeyValuePair<K extends string, T> = MyRecord<K, T>;  
  
function createObject<K extends string, T>(key: K, value: T): KeyValuePair<K, T> {  
    return { [key]: value } as any; // 这里使用 as any 是为了绕过 TypeScript 的限制,实际上应该使用更安全的方式来确保 key 是有效的属性名  
}  
  
const result = createObject('key', 'value'); // result 的类型是 { key: string }

答案

type MyRecord<T extends keyof any, K> = {
    [P in  T]: K
}

28. MyAwaited获取泛型参数

// 假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型
// 。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 
// 返回的类型。请你实现一个类型,可以获取这个类型。

// 例如:Promise<ExampleType>,请你返回 ExampleType 类型。
type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

答案

type MyAwaited<T> = T extends Promise<infer U> ? U : never

29. Promise.all 中等

// Promise.all
// 给函数PromiseAll指定类型,它接受元素为 Promise
// 或者类似 Promise 的对象的数组,返回值应为Promise<T>,
// 其中T是这些 Promise 的结果组成的数组。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});



// 应推导出 `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)


答案

declare function PromiseAll<T extends any[]>(values: readonly [...T]):
  Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;

30. Flatten 扁平化数组

// 在这个挑战中,你需要写一个接受数组的类型,
// 并且返回扁平化的数组类型。
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, 5]

答案

type Flatten<
  T extends any[],
  U extends any[] = [],
  F extends any[] = []
> = T extends [infer first, ...infer other]
  ? Flatten<
      other,
      first extends any[] ? [...U, ...F] : [...U, ...F, first],
      first extends any[] ? Flatten<first> : []
    >
  : [...U, ...F];

31. 计算字符串

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值