LeetCode 1: 两数之和 TypeScript笔记
从 LeetCode 实战入手,从零学习 TypeScript。记录了实现过程中的笔记和疑问。
1. 实现代码
function twoSum(nums: number[], target: number): number[] {
const hashtable = new Map<number, number>();
const n = nums.length;
for (let i = 0; i < n; ++i) {
const num = nums[i];
const index = hashtable.get(target - num);
// Equality Narrowing
if (index !== undefined) {
return [i, index];
}
hashtable.set(num, i);
}
return [];
}题目保证恰好存在一组解,且同一个元素不能使用两次;末尾的 return [] 仅作无解时的兜底处理。题目说明
2. 笔记
2.1 let、var、const 和变量类型
TS 中常用的变量声明方式是 let、var 和 const。变量名后可以使用 : T 标注类型,例如 let num: number = 0 或 let arr: number[] = []。有初始化值时,通常可以省略类型标注,由编译器推断;之后的赋值仍需符合显式声明或推断出的类型。
2.1.1 let, var, const
| 写法 | 作用域 | 能否重新赋值 | 常用选择 |
|---|---|---|---|
let | 块作用域 | 可以 | 需要改变变量的值 |
const | 块作用域 | 不可以 | 默认优先使用 |
var | 在函数内声明时为函数作用域,不受普通 {} 限制 | 可以 | 旧代码常见,新代码通常避免 |
let i = 0;
const table = new Map<number, number>();const 是变量声明关键字,用于建立不能重新赋值的绑定,不能与 let、var 叠加使用。C++ 的 const 是类型限定符,两者的作用不同。
另外,const 限制的是变量重新赋值,仍允许修改对象内容:
const table = new Map<number, number>();
table.set(8, 0); // 可以:修改现有对象
table = new Map<number, number>(); // 报错:重新赋值这也是工程中经常使用 const 声明容器的原因。参考文档
2.1.2 声明和定义变量
TypeScript 提供静态类型检查,后续赋值必须符合变量的声明类型。例如,声明为 number | string 就允许数值和字符串;下面的 i 被推断为 number,因此不能赋值为字符串。类型标注会在生成 JavaScript 时被擦除,不会自动产生运行时的数据校验。
let i = 0; // 推断为 number
let j: number = 0; // 显式指定 number
i = "hello"; // 类型检查报错上例省略类型标注时的类型推断,类似于 C++ 的 auto;let 本身是变量声明关键字,推断得到的类型仍会约束后续赋值。
像 twoSum 这样的独立函数,一般显式标注参数类型。回调参数可以由上下文推断,有默认值的参数也可以从默认值推断;没有这些依据且未标注类型时,启用 noImplicitAny 会报错。返回类型可以从返回表达式推断,也可以显式标注以明确接口约定。类型推断
2.2 new Map():采用构造器创建对象
TS 中,带初始化表达式的变量声明会把右侧表达式的结果绑定到变量名:
const nums: number[] = []; // 数组字面量创建数组
const table = new Map<number, number>(); // 构造器创建 Map数组有 [] 这种字面量语法;Map 没有专属字面量语法,构造器要求使用 new,直接调用 Map() 会报错。Map<number, number> 中的两个类型参数分别指定键和值的类型。Map 构造器
这里的 new 表示构造调用,对象内存由垃圾回收机制管理。JavaScript 的 delete 用于删除对象属性,并不是与 new 配对的内存释放操作,因此不能照搬 C++ 中配对管理对象内存的 new / delete 写法。内存管理、delete 运算符
2.3 类型收窄
Map.get(key) 返回键对应的值,键不存在时返回 undefined。本例的值类型为 number,因此 get() 的返回类型是 number | undefined。get 方法
Map.has(key) 检查键是否存在。通用的 Map 可以存储 undefined 值,因此键存在不一定意味着值不是 undefined;本例的 Map<number, number> 不包含这类值,但 TypeScript 也不会根据 has() 的检查,把随后一次 get() 调用的返回类型收窄为 number。has 方法
下面是本例中两个方法的类型签名片段,省略所属接口:
has(key: number): boolean;
get(key: number): number | undefined;具体到题目:
const index = hashtable.get(target - num); // number | undefined
if (index !== undefined) {
return [i, index]; // 此分支中,index 已确定为 number
}TS 根据条件排除了变量 index 的 undefined 分支,这叫做类型收窄。这里的惯用思路是:先取得可能缺失的值,再检查它,随后使用这个已经检查过的值。 重新调用 get() 会得到一个仍可能是 undefined 的结果,之前对 index 的检查不会收窄这次调用。显式比较 undefined 也能正常保留下标 0。类型收窄