其实 ts 相关的面试题目很少,常问的就那么几个,但是呢,有这么个问题。就是如果面试官问我范型是什么,可能我还能说上几句,但是如果他直接说,说说你对 ts 的理解?为什么要用 ts 这样以来就有点迷茫了,一时之间竟然不知道怎么回到。
我承认是我的原因,总结完这篇博客,如果再有面试题目没有回答上来那我就。。。。
对,没错,我还没找到工作。
ts 相关的面试题都是概念相关的,我是没有遇到过写代码的,所以一定要组织好自己的语言,尤其是我这种表达能力差的。
一、为什么
1.1 为什么要使用 ts
一句【ts】提供了类型检查,肯定是不够的,可以对应着 js 的功能来想 ts 改进的点。
- ts 是 js 的超集
- ts 提供静态类型检查,有利于减少错误
- ts 可以提高代码的可读性和可维护性
- ts 提供了多种开发工具,类型推断等,可以提高开发效率
- ts 有强大的类型系统,比如 interface、type、和范型
- ts 中的类 class 相对于 js 增加了范型,访问控制符private、public、protected
- ts 有类型体操,比如 pick 、omit、Partial 等【类型体操这个概念要记住】
- ts 编译后成为 js 代码
要用自己的话总结一下,多罗列几个 ts 有而 js 中没有的功能。
1.2 ts 相对于 js 有什么优点,ts 有哪些新增加的功能
我觉得这个问题和上一个问题的答案是差不多的,重点罗列一下 ts 中的重要功能
- ts 中可以使用范型定义接口、函数、类
- ts 中有接口 interface 的概念,可以用来描述一个对象、函数、类
- ts 中可以使用 type 来定义一个对象、函数和变量
- ts 中的类相对于 js 增加了范型,访问控制符private、public、protected
- ts 提供枚举类型 enum
- ts 提供方法重载功能,定义同名但是不同参数数量/类型和返回值的名函数签名,然后在后面定义一个实体函数涵盖不同签名的情况。可以是函数的使用更加灵活。
二、怎么用
2.1 接口你一般是怎么用的
ts 中的接口的定义使用的是 interface 这个关键字,作用
- 用来定义对象结构和类型的抽象方式,不具体实现
- 可以用在函数参数,类等各种地方
- 可以用于类的属性和方法检查,class 使用 implements 关键字来应用接口
- 接口可以使用 extends 关键字继承
- 重复定义的同名接口可以进行属性的合并
-
interface Animal {name: string;speak(): void; }class Dog implements Animal {name: string;constructor(name: string) {this.name = name;}speak() {console.log(`${this.name} barks.`);} }
2.2 keyof 关键字怎么用
一句话,keyof 用于把一个【对象类型】的健组成一个联合类型。【记住联合类型这个单词】,keyof 作用域一个类型上面,返回的也是一个类型。
keyof 用于返回一个对象类型键组成的联合类型,有点类似于 Object.keys, 只不过前者作用在类型上,返回的是一个联合类型;后者作用在对象上,返回的是数组。
可以使用 keyof 访问对象的属性,还可以用于范型约束,最常用在函数的参数中,除此之外还可以创建各种联合类型。
这个时候就需要举一个例子,可以举 keyof 作为函数参数在范型约束中的应用,看下面的代码,用自己的话说一下。
function fn<T, K extends keyof T>(obj: T, key: K):T[K] {return obj[key]
}
- 比如我我们要实现一个函数,用于访问一个对象的值
- 可以定一个一个函数,传入两个参数,第一个参数是对象,第二个参数是 key
- 这个时候我们就需要使用范型约束一下,保证第二个参数时key 时第一个参数对象的key 的类型集合
- 定义对象为T,定义K extends keyof T
- 这样在函数中就可以使用两个参数和方括号,访问对象的属性了
详情可以参考这篇文章。
2.3 is 操作符怎么用
is 操作符是用于类型保护的,不能用在条件判断语句中,通常用在函数的返回值,用于判断一个对象是否属于某个类型。
- 左操作符是对象
- 右操作符是类型
- 用在函数的返回值
- 显式的指定返回值类型是一个布尔值
2.4 ts 中的命名空间和模块怎么用
ts 中模块和 js 模块用途一样,都是可以使用 exports / import 关键字进行变量或者类型的导出。只不过在 ts 中还可以导入、导出类型!
ts 中的模块和 js 中的模块一样,任何包含顶级import 或者export的文件都会当成一个模块,如果不带import或者export, 那么他的内容就会被视为全局的。
请看代码
关键是这行代码
Object.defineProperty(exports, "__esModule", { value: true });
这行代码通常出现在 TypeScript 编译后生成的 JavaScript 代码中,它的作用是标记一个模块为 ES 模块。
具体来说:
Object.defineProperty
是 JavaScript 中用来定义对象属性的方法之一。exports
是 CommonJS 模块系统中的一个对象,用于导出模块中的内容。"__esModule"
是一个特殊的属性名,用于指示当前模块是否为 ES 模块。{ value: true }
是一个对象字面量,表示将"__esModule"
属性的值设置为true
。在 TypeScript 中,当你使用 ES 模块的语法(如
export
和import
)导出和导入模块时,编译器会自动在输出的 JavaScript 代码中添加这行代码,以确保该模块被正确地标记为 ES 模块。这样做是为了在使用 CommonJS 模块系统加载 ES 模块时能够正确识别和处理它们。
这个时候就会问到 esm 和 commonjs 的区别了
- 语法不同 cjs 使用 modules.export / require
- esm 使用的是 import 、export 语法
- esm 导出值的引用,cjs 导出值的拷贝
- esm 是静态语法,编译的时候就知道哪些模块被使用,所以支持tree-shaking
- cjs 是动态语法,执行的时候才知道哪些模块被使用,不支持tree-skaking
- 具体参考这篇文章
命名空间,用于解决重名问题,不通命名空间之间的变量互不影响,同名也不会发生冲突。
- 可以使用命名空间名调用空间内的各种属性、类型、函数等
- 需要在外部使用命名空间的的属性,需要加上 export ,意味着导出当前命名空间
- 命名空间本质是一个对象,将一些列变量映射到一个对象的属性上
- 正常项目中很少用命名空间,多用于在.d.ts 文件中标记js库类型的时候使用,主要作用是给编译器编写代码的时候参考使用
还有一个常用的关键字是 declare,也是比较常用于 js 库,常用在 d,ts 文件中,为了编辑器不报错。
在 TypeScript 中,
declare
关键字用于告诉编译器关于某些实体(通常是变量、函数、类、接口等)的类型信息,但它并不会真正创建这些实体。通常情况下,declare
用于描述一些已经存在于你的代码运行环境中的实体,比如全局变量、函数、或者库的类型定义。
2.5 ts 中的范型怎么用
这个是考的最多的题目,能回答上来是提高函数的复用性,但是不够,我们需要举个例子。
先说范型可以用在什么地方,然后再说怎么用,然后说有什么好处,最后一定要举个实际的例子,用自己的话总结一下。
- 我们在定义变量、函数、接口和类的时候都可以使用范型【用在什么地方】
- 在定义的时候不确定具体类型如 number 之类的,而是用一个占位符来指代某个类型,使用紧跟着函数名的【尖括号】来声明范型,【怎么用】
- 在实际使用函数、类或者类的时候才确定,参数、返回值、或者变量的类型,这样我们就不用重复声明多个类型不同的函数。
- 除了普通用法,还可以使用 extends + keyof 来做范型约束【怎么用】
- 使用范型可以减少代码的重复性,提高代码的可复用性【有什么好处】
- 比如说我们有一个针对数组排序的方法,数组可能是字符串数组,也可以是数字数组,那么我门在定义这个方法的时候就可以使用范型,不预先定义数组元素的内容.这样针对不同类型的数字,我们可以只定义一个方法即可,减少很多工作量。
-
function sortArray<T>(arr: T[]): T[] {return arr.slice().sort(); }
这个范型每次都觉得说的挺好的,但是每次感觉面试官都不满意,愁死了。
2.6 元组是什么,怎么用
元组相当于一个可以装不同类型的数据的数组,可以使用数组的各种方法,但是元组和数组有以下不同点
- 对元组进行赋值或初始化的时候,要提供所有类型的项,而且位置要和类型的位置相对应
- 元组在初始化的时候,所有的类型和长度就确定了的
- 元组使用push 等方法扩展的时候,必须是已有类型的联合类型
三、区别
ts 中第二常见的面试题目就是问区别,因为 ts 中两个相似的关键字比较多,一定要注意。
3.1 ts 和 js 的区别
这个内容和第一章的答案一样,主要是说一下 ts 的好处,和ts 新增加的特性即可,不再赘述!
3.2 type 和 interface 的区别
这个是出现频率极高的题目,主要区别是
- interface 一般用来定义对象类型,且重复定义内容会合并,class 可以使用 implements 引用接口,使用 extends 关键字进行扩展
- type 可以定义任何数据类型,重复定义不会合并,会报错,使用&来创建联合类型和交叉类型
- 具体的区别可以参考这篇文章
3.3 never 和 void 的区别
没有返回类型 vs 永远不会发生,二者都常用于声明函数的返回值。
- never 表示永远不会发生,通常用于【抛出异常】或者【无限循环】,never可以赋值给任意类型,但是要给一个 never 类型的变量赋值,只能使用函数返回值的形式。
- void 代表没有返回任何类型,常用于函数没有返回值的情况,可以将 void 赋值给变量,但是这个变量只能接受 null 和 undefined或者any; 可以给 void 类型初始化赋值为 undefined
- 具体参考这篇文章。
3.4 any 和 unknown 的区别
- any 代表不做任何类型检查,类似写 js 代码,任何类型都可以赋值为any,any 也可以给任何类型赋值.
- unknown 代表未知类型, 任意类型都可以赋值为 unknown,但是 unknown 不能赋值给已知类型
- 具体参考这篇文章。
3.5 模块和命名空间的区别
ts 中的模块和 js 中的 esm 模块类似,都可以使用import / export 来导入导出,但是 ts 除了可以导出变量,也可以导出类型。
命名空间常用于避免命名冲突,使用命名空间来达到作用域隔离的效果,命名空间内的变量如果要在外部使用需要使用 export 进行导出,具体可以参照2.4的内容。
四、总结
内容不是很多,可能也不全面,但是还是记不住,特别是区别那块,可能会记住一部分,但是表达不清楚,这就需要多下功夫了。
内容仅供参考,如果有问题欢迎指出,因为我也还没有找到工作。