1.面试官:说说你对 TypeScript 中类的理解?应用场景?
一、是什么
类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础
类是一种用户定义的引用数据类型,也称类类型
传统的面向对象语言基本都是基于类的,JavaScript
基于原型的方式让开发者多了很多理解成本
在 ES6
之后,JavaScript
拥有了 class
关键字,虽然本质依然是构造函数,但是使用起来已经方便了许多
但是 JavaScript
的class
依然有一些特性还没有加入,比如修饰符和抽象类
TypeScript
的 class
支持面向对象的所有特性,比如 类、接口等
二、使用方式
定义类的关键字为 class
,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
- 字段: 字段是类里面声明的变量。字段表示对象的有关数据。
- 构造函数: 类实例化时调用,可以为类的对象分配内存。
- 方法: 方法为对象要执行的操作
如下例子:
class Car {
// 字段
engine:string;
// 构造函数
constructor(engine:string) {
this.engine = engine
}
// 方法
disp():void {
console.log("发动机为 : "+this.engine)
}
}
复制代码
继承
类的继承使用过extends
的关键字
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
复制代码
Dog
是一个 派生类,它派生自 Animal
基类,派生类通常被称作子类,基类通常被称作 超类
Dog
类继承了Animal
类,因此实例dog
也能够使用Animal
类move
方法
同样,类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写,通过super
关键字是对父类的直接引用,该关键字可以引用父类的属性和方法,如下:
class PrinterClass {
doPrint():void {
console.log("父类的 doPrint() 方法。")
}
}
class StringPrinter extends PrinterClass {
doPrint():void {
super.doPrint() // 调用父类的函数
console.log("子类的 doPrint()方法。")
}
}
复制代码
修饰符
可以看到,上述的形式跟ES6
十分的相似,typescript
在此基础上添加了三种修饰符:
- 公共 public:可以自由的访问类程序里定义的成员
- 私有 private:只能够在该类的内部进行访问
- 受保护 protect:除了在该类的内部可以访问,还可以在子类中仍然可以访问
私有修饰符
只能够在该类的内部进行访问,实例对象并不能够访问
并且继承该类的子类并不能访问,如下图所示:
受保护修饰符
跟私有修饰符很相似,实例对象同样不能访问受保护的属性,如下:
有一点不同的是 protected
成员在子类中仍然可以访问
除了上述修饰符之外,还有只读修饰符
只读修饰符
通过readonly
关键字进行声明,只读属性必须在声明时或构造函数里被初始化,如下:
除了实例属性之外,同样存在静态属性
静态属性
这些属性存在于类本身上面而不是类的实例上,通过static
进行定义,访问这些属性需要通过 类型.静态属性 的这种形式访问,如下所示:
class Square {
static width = '100px'
}
console.log(Square.width) // 100px
复制代码
上述的类都能发现一个特点就是,都能够被实例化,在 typescript
中,还存在一种抽象类
抽象类
抽象类做为其它派生类的基类使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法,如下所示:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
复制代码
这种类并不能被实例化,通常需要我们创建子类去继承,如下:
class Cat extends Animal {
makeSound() {
console.log('miao miao')
}
}
const cat = new Cat()
cat.makeSound() // miao miao
cat.move() // roaming the earch...
复制代码
三、应用场景
除了日常借助类的特性完成日常业务代码,还可以将类(class)也可以作为接口,尤其在 React
工程中是很常用的,如下:
export default class Carousel extends React.Component<Props, State> {}
复制代码
由于组件需要传入 props
的类型 Props
,同时有需要设置默认 props
即 defaultProps
,这时候更加适合使用class
作为接口
先声明一个类,这个类包含组件 props
所需的类型和初始值:
// props的类型
export default class Props {
public children: Array<React.ReactElement<any>> | React.ReactElement<any> | never[] = []
public speed: number = 500
public height: number = 160
public animation: string = 'easeInOutQuad'
public isAuto: boolean = true
public autoPlayInterval: number = 4500
public afterChange: () => {}
public beforeChange: () => {}
public selesctedColor: string
public showDots: boolean = true
}
复制代码
当我们需要传入 props
类型的时候直接将 Props
作为接口传入,此时 Props
的作用就是接口,而当需要我们设置defaultProps
初始值的时候,我们只需要:
public static defaultProps = new Props()
复制代码
Props
的实例就是 defaultProps
的初始值,这就是 class
作为接口的实际应用,我们用一个 class
起到了接口和设置初始值两个作用,方便统一管理,减少了代码量
参考文献
2.面试官:说说你对 TypeScript 的理解?与 JavaScript 的区别?
一、是什么
TypeScript
是 JavaScript
的类型的超集,支持ES6
语法,支持面向对象编程的概念,如类、接口、继承、泛型等
超集,不得不说另外一个概念,子集,怎么理解这两个呢,举个例子,如果一个集合 A 里面的的所有元素集合 B 里面都存在,那么我们可以理解集合 B 是集合 A 的超集,集合 A 为集合 B 的子集
其是一种静态类型检查的语言,提供了类型注解,在代码编译阶段就可以检查出数据类型的错误
同时扩展了 JavaScript
的语法,所以任何现有的 JavaScript
程序可以不加改变的在 TypeScript
下工作
为了保证兼容性,TypeScript
在编译阶段需要编译器编译成纯 JavaScript
来运行,是为大型应用之开发而设计的语言,如下:
ts
文件如下:
const hello: string = "Hello World!";
console.log(hello);
复制代码
编译文件后:
const hello = "Hello World!";
console.log(hello);
复制代码
二、特性
TypeScript
的特性主要有如下:
- 类型批注和编译时类型检查:在编译时批注变量类型
- 类型推断:ts 中没有批注变量类型会自动推断变量的类型
- 类型擦除:在编译过程中批注的内容和接口会在运行时利用工具擦除
- 接口:ts 中用接口来定义对象类型
- 枚举:用于取值被限定在一定范围内的场景
- Mixin:可以接受任意类型的值
- 泛型编程:写代码时使用一些以后才指定的类型
- 名字空间:名字只在该区域内有效,其他区域可重复使用该名字而不冲突
- 元组:元组合并了不同类型的对象,相当于一个可以装不同类型数据的数组
- ...
类型批注
通过类型批注提供在编译时启动类型检查的静态类型,这是可选的,而且可以忽略而使用 JavaScript
常规的动态类型
function Add(left: number, right: number): number {
return left + right;
}
复制代码
对于基本类型的批注是 number
、bool
和 string
,而弱或动态类型的结构则是 any
类型
类型推断
当类型没有给出时,TypeScript 编译器利用类型推断来推断类型,如下:
let str = "string";
复制代码
变量 str
被推断为字符串类型,这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时
如果缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 any
类型
接口
接口简单来说就是用来描述对象的类型 数据的类型有 number
、null
、 string
等数据格式,对象的类型就是用接口来描述的
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: "Tom",
age: 25,
};
复制代码
三、区别
- TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法
- TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译
- TypeScript 文件的后缀名 .ts (.ts,.tsx,.dts),JavaScript 文件是 .js
- 在编写 TypeScript 的文件的时候就会自动编译成 js 文件
更多的区别如下图所示:
参考文献
3.面试官:说说 typescript 的数据类型有哪些?
一、是什么
typescript
和 javascript
几乎一样,拥有相同的数据类型,另外在javascript
基础上提供了更加实用的类型供开发使用
在开发阶段,可以为明确的变量定义为某种类型,这样typescript
就能在编译阶段进行类型检查,当类型不合符预期结果的时候则会出现错误提示
二、有哪些
typescript
的数据类型主要有如下:
- boolean(布尔类型)
- number(数字类型)
- string(字符串类型)
- array(数组类型)
- tuple(元组类型)
- enum(枚举类型)
- any(任意类型)
- null 和 undefined 类型
- void 类型
- never 类型
- object 对象类型
boolean
布尔类型
let flag:boolean = true;
// flag = 123; // 错误
flag = false; //正确
复制代码
number
数字类型,和javascript
一样,typescript
的数值类型都是浮点数,可支持二进制、八进制、十进制和十六进制
let num:number = 123;
// num = '456'; // 错误
num = 456; //正确
复制代码
进制表示:
let decLiteral: number = 6; // 十进制
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制
复制代码
string
字符串类型,和JavaScript
一样,可以使用双引号("
)或单引号('
)表示字符串
let str:string = 'this is ts';
str = 'test';
复制代码
作为超集,当然也可以使用模版字符串``进行包裹,通过 ${} 嵌入变量
let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }
复制代码
array
数组类型,跟javascript
一致,通过[]
进行包裹,有两种写法:
方式一:元素类型后面接上 []
let arr:string[] = ['12', '23'];
arr = ['45', '56'];
复制代码
方式二:使用数组泛型,Array<元素类型>
:
let arr:Array<number> = [1, 2];
arr = ['45', '56'];
复制代码
tuple
元祖类型,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
let tupleArr:[number, string, boolean];
tupleArr = [12, '34', true]; //ok
typleArr = [12, '34'] // no ok
复制代码
赋值的类型、位置、个数需要和定义(生明)的类型、位置、个数一致
enum
enum
类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
复制代码
any
可以指定任何类型的值,在编程阶段还不清楚类型的变量指定一个类型,不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查,这时候可以使用any
类型
使用any
类型允许被赋值为任意类型,甚至可以调用其属性、方法
let num:any = 123;
num = 'str';
num = true;
复制代码
定义存储各种类型数据的数组时,示例代码如下:
let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;
复制代码
null 和 和 undefined
在 JavaScript
中 null
表示 "什么都没有",是一个只有一个值的特殊类型,表示一个空对象引用,而undefined
表示一个没有设置值的变量
默认情况下null
和undefined
是所有类型的子类型, 就是说你可以把 null
和 undefined
赋值给 number
类型的变量
let num:number | undefined; // 数值类型 或者 undefined
console.log(num); // 正确
num = 123;
console.log(num); // 正确
复制代码
但是ts
配置了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自
void
用于标识方法返回值的类型,表示该方法没有返回值。
function hello(): void {
alert("Hello Runoob");
}
复制代码
never
never
是其他类型 (包括null
和 undefined
)的子类型,可以赋值给任何类型,代表从不会出现的值
但是没有类型是 never 的子类型,这意味着声明 never
的变量只能被 never
类型所赋值。
never
类型一般用来指定那些总是会抛出异常、无限循环
let a:never;
a = 123; // 错误的写法
a = (() => { // 正确的写法
throw new Error('错误');
})()
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
复制代码
object
对象类型,非原始类型,常见的形式通过{}
进行包裹
let obj:object;
obj = {name: 'Wang', age: 25};
复制代码
三、总结
和javascript
基本一致,也分成:
- 基本类型
- 引用类型
在基础类型上,typescript
增添了void
、any
、emum
等原始类型