每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
Tuple To Union
实现泛型 TupleToUnion<T>,它返回元组所有值的合集。
例如:
type Arr = ["1", "2", "3"];
type Test = TupleToUnion<Arr>; // expected to be '1' | '2' | '3'
分析
显然这是一个降维的过程,一个 1 维数组降级为 0 维,通常我们使用递归的方式实现,假想的过程如下:
数组长度大于 0 ? ['1', '2', '3']
数组长度大于 0 ? ['1', '2'] | '3'
数组长度大于 0 ? ['1'] | '2' | '3'
数组长度等于 0 ? [] | '1' | '2' | '3'
'1' | '2' | '3'
尝试写出
type TupleToUnion<T extends string[], K = never> = {
0: K;
1: T extends [...infer Rest, infer Last]
? Rest extends string[]
? TupleToUnion<Rest, K | Last>
: never
: never;
}[T["length"] extends 0 ? 0 : 1];
简化写法
type TupleToUnion<T extends string[], K = never> = T["length"] extends 0
? K
: T extends [...infer Rest, infer Last]
? Rest extends string[]
? TupleToUnion<Rest, K | Last>
: never
: never;
测试用例
type C = TupleToUnion<["1", "2", "3"]>; // "1" | "2" | "3"
参考答案
type TupleToUnion<T extends unknown[]> = T[number];
type C = TupleToUnion<["1", "2", "3"]>; // "1" | "2" | "3"
经验总结
never在类型联合的时候是透明的。T[number]自带降维的作用,union 过程是自动进行的。
2. 两个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 最大连续 1 的个数
给定一个二进制数组 nums , 计算其中最大连续 1 的个数。
示例 1:
输入:nums = [1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.
示例 2:
输入:nums = [1,0,1,1,0,1]
输出:2
提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1.
尝试实现:
var findMaxConsecutiveOnes = function (nums) {
var max = 0;
var curretMax = 0;
var len = nums.length;
for (var i = 0; i < len; i++) {
if (nums[i]) {
curretMax++;
} else {
max = curretMax > max ? curretMax : max;
curretMax = 0;
}
}
return curretMax > max ? curretMax : max;
};
我的思路:
-
初始化两个变量
max和curretMax为 0。max用于存储迄今为止找到的最长连续 1 的数量,而curretMax用于记录当前连续 1 的数量。 -
通过一个 for 循环遍历整个数组
nums。 -
在循环内部:
- 如果当前元素
nums[i]等于 1,则curretMax加 1。 - 如果当前元素不等于 1(即数组中的 0),则执行以下操作:
- 更新
max为curretMax和max中的较大值。 - 重置
curretMax为 0,因为连续序列被打断。
- 更新
- 如果当前元素
-
在循环结束后,再次检查并更新
max,以确保覆盖最后一次连续 1 的计数(如果数组以 1 结尾)。 -
函数返回
max,即数组中连续 1 的最大数量。
后记:代码中 curretMax 存在拼写错误,应该是 currentMax。
2.2 提莫攻击
在《英雄联盟》的世界中,有一个叫 “提莫” 的英雄。他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态。
当提莫攻击艾希,艾希的中毒状态正好持续 duration 秒。
正式地讲,提莫在 t 发起攻击意味着艾希在时间区间 [t, t + duration - 1](含 t 和 t + duration - 1)处于中毒状态。如果提莫在中毒影响结束 前 再次攻击,中毒状态计时器将会 重置 ,在新的攻击之后,中毒影响将会在 duration 秒后结束。
给你一个 非递减 的整数数组 timeSeries ,其中 timeSeries[i] 表示提莫在 timeSeries[i] 秒时对艾希发起攻击,以及一个表示中毒持续时间的整数 duration 。
返回艾希处于中毒状态的 总 秒数。
示例 1:
输入:timeSeries = [1,4], duration = 2
输出:4
解释:提莫攻击对艾希的影响如下:
- 第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
- 第 4 秒,提莫再次攻击艾希,艾希中毒状态又持续 2 秒,即第 4 秒和第 5 秒。
艾希在第 1、2、4、5 秒处于中毒状态,所以总中毒秒数是 4 。
示例 2:
输入:timeSeries = [1,2], duration = 2
输出:3
解释:提莫攻击对艾希的影响如下:
- 第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
- 第 2 秒,提莫再次攻击艾希,并重置中毒计时器,艾希中毒状态需要持续 2 秒,即第 2 秒和第 3 秒。
艾希在第 1、2、3 秒处于中毒状态,所以总中毒秒数是 3 。
提示:
1 <= timeSeries.length <= 104
0 <= timeSeries[i], duration <= 107
timeSeries 按 非递减 顺序排列
尝试完成:
/**
* @param {number[]} timeSeries
* @param {number} duration
* @return {number}
*/
var findPoisonedDuration = function (timeSeries, duration) {
var len = timeSeries.length;
var acc = 0;
function min(a, b) {
return a > b ? b : a;
}
for (var i = 0; i < len; i++) {
if (i === len - 1) {
acc += duration;
} else {
acc += min(duration, timeSeries[i + 1] - timeSeries[i]);
}
}
return acc;
};
console.log(findPoisonedDuration([1, 2, 2, 3, 7], 3)); // 8
我的思路:
-
初始化:定义一个变量
len存储timeSeries数组的长度,以及一个变量acc用于累计计算毒药作用的总时长,初始值为 0。 -
辅助函数:定义一个内部函数
min(a, b),用于返回两个数a和b中的较小值。 -
遍历数组:使用一个 for 循环遍历
timeSeries数组。 -
处理当前时间点:
- 如果当前索引
i等于数组长度减去 1(即数组的最后一个元素),则直接将duration加到acc上。这是因为在数组的最后一个时间点,毒药可以持续作用到duration结束,没有后续时间点限制毒药的作用时间。 - 如果当前索引
i不是最后一个元素,计算当前时间点之后毒药的持续时间。使用min函数获取duration和timeSeries[i + 1] - timeSeries[i](即当前时间点与下一个时间点之间的时间差)中的较小值,并将结果加到acc上。这样做是为了确保毒药作用时间不会超过到下一个时间点的时间。
- 如果当前索引
-
返回结果:循环结束后,返回
acc,即毒药作用的总时长。
这个函数的核心在于计算每个时间点上毒药的作用时间,并累加这些时间以得到总时长。对于数组中的最后一个时间点,毒药可以完整地作用 duration 时长,因为后面没有时间点限制它。对于其他时间点,毒药的作用时间可能受到下一个时间点的限制,因此需要取 duration 和两个时间点间隔中的较小值。
3. 三个前端题目
- js 中的数据类型检测方法都有哪些?
首先,用于检测 js 中数据类型的方法一般来说有四种,分别为:
tyepof xx instanceof yx.constructor?.nameObject.prototype.toString.call(x).slice(8, -1);
前两个是操作符,中间的是属性,而最后一个是方法。
- 先说 typeof: typeof x 的返回值可能为:
'object' 'undefined' 'number' 'string' 'symbol' 'bigint' 'boolean' 'function'。注意它们都是首字母小写的字符串。
- 使用
typeof x判断类型有两个问题,问题一在于:对 null 的判断结果是'object',这个是不正确的;第二个问题是:只能判断出对象的类型为'object',无法进一步得到更加具体的结论。 - typeof x 有一个优点那就是在判断基本类型的时候非常的方便(除了 null),并且在判断
'function'类型的时候也非常方便(唯一一个给出具体类型的 object)。 - 对于 typeof x 判断方法的缺点的处理方法就是采用更加合适的其它判别方法。
x instanceof y判别方法的问题在于只对 object 类型生效,只能判断表达式是否成立,而不能直接告诉调用者到底是谁。其原理是通过原型链查找,所以 y 只要在 x 的原型链上都会返回 true,总之这种判别法只能用来排除,因为给出的结论都是模糊性的。?.constructor?.name这种方法对于对象类型和包装类型都生效,返回的是首字母大写的字符串。缺点在于无法分辨 null 和 undefined,并且 constructor 属性值可以被篡改。不考虑其缺点的时候比 typeof 好用一些。- 最后的
Object.prototype.toString.call(x).slice(8, -1)方法是王炸,是唯一一种能够判断出 null 数据类型的方法。返回值为首字母大写的字符串,列举其可能的返回值:'String' 'Null' 'Undefined' 'Number' 'String' 'Symbol' 'Bigint' 'Boolean' 'Array' 'Date' 'RegExp' 'Error' 'Function'。可惜这个方法还是有缺点的const a = {};a 可以通过修改[Symbol.toStringTag]的值改变此方法的返回值。
总结下来,这四个方法各有各的用武之地,但是如果想要清晰的知道 x 的类型的话,推荐使用最后一种方式。
- 判断 x 为数组的方法都有哪些? 总结下来就是:toString 方法,ES6 方法和原型相关的方法。
-
Object.prototype.toString.call(x).slice(8, -1) === "Array";
-
Array.isArray(x);
-
x instanceof Array;
-
x.__proto__ == Array.prototype;
-
x.constructor.name === "Array";
-
Array.prototype.isPrototypeof(x);
- null 和 undefined 的区别是什么?
-
- 从语义上:null 表示有值,但是值为空,而 undefined 则表示没有值。
-
- 从语法上:形参或者变量的值为 null 的时候并不会触发默认值,但是如果值为 undefined 会强制触发默认值。
-
- typeof检测的时候,一个结果是正确的,另外一个结果不正确。
-
- 两者虽然都是基本类型,但是 undefined 不是保留字,这意味着你可以为其赋值,或者声明其为变量,这是不安全的,所以一般使用
void 0作为undefined的替代。
- 两者虽然都是基本类型,但是 undefined 不是保留字,这意味着你可以为其赋值,或者声明其为变量,这是不安全的,所以一般使用
4.四句英语积累
- brainstorm
- Let's brainstorm some ideas for the new campaign
[kæmˈpeɪn]. campaign 在这里做【活动】来讲。 - We need to start brainstorming solutions to the problem right now!
- review
- I think we need more time to review the situation.
- Robert, have you already reviewed the new IT security guidelines? 【IT 安全指导方针】