题目十六:在 TypeScript 中,比较运算符 == 和 === 有什么不同?
一、讲解视频
二、题目解析
此问题在 js 中也存在,不是 ts 中特有的问题,和=的主要区别是判断时是否检查对应的数据类型,js 中==判断的时候会进行类型转换后再尝试比较内容是否相等,若转换后的值相同,则视为相等;===是比较严格的相等判断运算符,会检查比较双方的值几类型,需要值和类型都相同才是相等的;!=和!==也有类似的规则;
三、参考资料
https://www.w3cschool.cn/javascript/js-comparisons.html
四、参考答案
- ==不进行类型检查,只判断值是否相等,判断时会进行类型转换,若转换后的值相等,则视为相等。
- ===较严格,会检测类型及值,都相同时才相等。
- !=和!==具有类似的规则。
题目十七:TypeScript 中 Type Guards(类型保护)的使用?
一、讲解视频
二、题目解析
本题目考察 ts 中的类型保护相关的知识,可通过类型保护确定联合类型变量的具体类型,可通过自定义函数、typeof 及 instanceof 进行具体类型的判断,类型保护是可以使用类型谓词指定具体的类型。
TypeScript里的 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。
要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词:
typescript">function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
在这个例子里, pet is Fish就是类型谓词。 谓词为 parameterName is Type这种形式,
parameterName必须是来自于当前函数签名里的一个参数名。 每当使用一些变量调用
isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
三、参考资料
https://www.tslang.cn/docs/handbook/advanced-types.html
四、参考答案
- 类型保护是运行时检查,用于缩小条件块中变量的类型范围,主要用于对联合类型时确定变量的具体类型。可以在函数和方法中使用使得根据输入类型不同表现不同,且不会丢失类型信息。
- 常见的类型保护包括使用 typeof、instanceof 和用户定义的类型保护函数。
题目十八:TypeScript 中可选属性如何设置及对应的使用场景?
一、讲解视频
二、题目解析
本题目考察 ts 中可选属性的相关知识,可选属性在属性后添加?进行标识,接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号。可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
三、参考资料
https://www.tslang.cn/docs/handbook/interfaces.html
四、参考答案
- TypeScript 中,使用? 符号将属性标记为可选,例如 str?: string。
- 对于不是必需的属性可以使用可选属性进行标识。
- 可以对可能存在的属性进行预定义,防止出现属性不存在的异常。
- 可以捕获引用了不存在的属性时的错误。
题目十九:TypeScript 中可辨识联合是什么?
一、讲解视频
二、题目解析
本题目考察对 ts 高级类型的可辨识联合的相关知识,可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合或 代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:
- 具有普通的单例类型属性— 可辨识的特征。
- 一个类型别名包含了那些类型的联合— 联合。
- 此属性上的类型保护。
首先我们声明了将要联合的接口。 每个接口都有 kind属性但有不同的字符串字面量类型。 kind属性称做 可辨识的特征或 标签。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起:
typescript">type Shape = Square | Rectangle | Circle;
现在我们使用可辨识联合:
typescript">function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
完整性检查
当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了 Triangle到 Shape,我们同时还需要更新 area:
typescript">type Shape = Square | Rectangle | Circle | Triangle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
// should error here - we didn't handle case "triangle"
}
有两种方式可以实现。 首先是启用 --strictNullChecks并且指定一个返回值类型:
typescript">function area(s: Shape): number { // error: returns number | undefined
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
因为 switch没有包涵所有情况,所以TypeScript认为这个函数有时候会返回 undefined。 如果你明确地指定了返回值类型为 number,那么你会看到一个错误,因为实际上返回值的类型为 number | undefined。 然而,这种方法存在些微妙之处且 --strictNullChecks对旧代码支持不好。
第二种方法使用 never类型,编译器用它来进行完整性检查:
typescript">function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
default: return assertNever(s); // error here if there are missing cases
}
}
这里, assertNever检查 s是否为 never类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case,那么 s将具有一个真实的类型并且你会得到一个错误。 这种方式需要你定义一个额外的函数,但是在你忘记某个case的时候也更加明显。
三、参考资料
https://www.tslang.cn/docs/handbook/advanced-types.html
四、参考答案
- 合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合或 代数数据类型。
- 当一个对象可以有多个形状但共享一个公共属性(通常是文字类型)时,可以使用它们,该属性可用于缩小变量具体的类型。
- 公共属性(通常称为“鉴别器”)允许我们在联合内的类型之间安全地切换,从而更轻松地使用此类对象。
题目二十:TypeScript 中的继承如何使用?
一、讲解视频
二、题目解析
本题目考察 ts 中继承的使用,ts 中类和接口都可以进行继承。
接口继承:
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
typescript">interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
一个接口可以继承多个接口,创建出多个接口的合成接口。
typescript">interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
类继承:
在TypeScript里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里, Dog是一个 派生类,它派生自 Animal 基类,通过 extends关键字。 派生类通常被称作 子类,基类通常被称作 超类。
typescript">class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
三、参考资料
https://www.tslang.cn/docs/handbook/interfaces.html
https://www.tslang.cn/docs/handbook/classes.html
四、参考答案
- TypeScript 支持继承,就像 ES6 类一样。使用extends关键字。
- 类可以继承另一个类的属性和方法,提高代码的可重用性并建立基类和派生类之间的关系。派生类还可以重写继承的方法或属性,甚至用新的方法或属性扩展对象结构。
- 接口也可以进行继承,继承后子接口具有父接口的所有属性。