每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
类型体操题目集合 FlatternDepth
递归将数组展开到指定的深度
示例:
type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>; // [1, 2, 3, 4, [5]]. flattern 2 times
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
分析
与完全的展开不同,我们需要展开到指定的深度,也就是我们需要一个计数器,而通常我们使用元组类型的 length 作为计数器,并且使用 extends 判断计数器是否到点。
尝试写出
type Unfold<T extends any[]> = T extends [infer F, ...infer R] ? (
F extends any[] ? [...F, ...Unfold<R>] : [F, ...Unfold<R>]
) : T;
type FlattenDepth<T extends any[], C extends number = 1, K extends any[] = []> = {
0: T,
1: FlattenDepth<Unfold<T>, C, [...K, unknown]>,
}[
K["length"] extends C ? 0 : 1
]
测试用例
type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>; // [1, 2, 3, 4, [5]]
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, [[5]]]
参考答案
type FlattenDepth<
T extends any[],
Depth extends number = 1,
Acc extends any[] = []
> = T extends [infer L, ...infer R]
? L extends any[]
? Acc['length'] extends Depth
? T
: [
...FlattenDepth<L, Depth, [any, ...Acc]>,
...FlattenDepth<R, Depth, Acc>
]
: [L, ...FlattenDepth<R, Depth, Acc>]
: T;
经验总结
- 可以先分开写,然后设法组装:
type FlattenDepth<T extends any[], C extends number = 1, K extends any[] = []> = {
0: T,
1: FlattenDepth<(T extends [infer F, ...infer R] ?
(F extends any[] ? [...F, ...Unfold<R>] : [F, ...Unfold<R>])
: T), C, [...K, unknown]>,
}[
K["length"] extends C ? 0 : 1
]
另外一个类型体操
FlipArguments
实现 lodash 中_.flip方法的类型版本
FlipArguments<T>类型接收泛型参数 T 并返回一个函数类型, 且此函数类型有和 T 相同的返回类型但其参数的顺序是倒过来的
type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>; // (arg0: boolean, arg1: number, arg2: string) => void
分析
在 ts 中,函数签名的形参名不重要,重要的是形参的类型;所以先提取 args 然后将其 reverse 即可。
尝试写出
type Reverse<T extends any[]> = T extends [infer F, ...infer R] ? [...Reverse<R>, F] : T;
type FlipArguments< T extends (...args: any[]) => any > = T extends (...args: infer A) => infer R ? (
(...args: Reverse<A>) => R
) : never;
测试用例
type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>; // (arg0: boolean, arg1: number, arg2: string) => void
参考答案
type FlipArray<T extends any[]> = T extends [...infer L, infer R]
? [R, ...FlipArray<L>]
: T;
type FlipArguments<T extends Function> = T extends (
...a: infer R
) => infer TReturn
? (...a: FlipArray<R>) => TReturn
: never;
经验总结
- 构造的辅助函数被递归了,因此很难将它们写在一起去。
2. 两个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 [273] 整数转换英文表示
将非负整数 num 转换为其对应的英文表示。
示例 1:
输入:num = 123
输出:"One Hundred Twenty Three"
示例 2:
输入:num = 12345
输出:"Twelve Thousand Three Hundred Forty Five"
示例 3:
输入:num = 1234567
输出:"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
提示:
0 <= num <= 231 - 1
尝试实现:
/**
* @param {number} num
* @return {string}
*/
var numberToWords = function (num) {
if (num === 0) return 'Zero';
if (num === 10) return 'Ten';
const b = Math.floor(num / 10 ** 9);
if (b) num -= b * 10 ** 9;
const m = Math.floor((num) / 10 ** 6);
if (m) num -= m * 10 ** 6;
const t = Math.floor((num) / 10 ** 3);
if (t) num -= t * 10 ** 3;
const g = num;
const red = {
"Billion": b,
"Million": m,
"Thousand": t,
"-1": g,
}
const digit = {
0:"",
1: "One",
2: "Two",
3: "Three",
4: "Four",
5: "Five",
6: "Six",
7: "Seven",
8: "Eight",
9: "Nine",
}
const digit2 = {
0:"",
1: "One",
2: "Twenty",
3: "Thirty",
4: "Forty",
5: "Fifty",
6: "Sixty",
7: "Seventy",
8: "Eighty",
9: "Ninety",
}
const digit3 = {
10: "Ten",
11: "Eleven",
12: "Twelve",
13: "Thirteen",
14: "Fourteen",
15: "Fifteen",
16: "Sixteen",
17: "Seventeen",
18: "Eighteen",
19: "Nineteen",
}
function getString(n) {
let _r = "";
const _b = Math.floor(n / 10 ** 2);
if (_b) n -= _b * 10 ** 2;
const _s = Math.floor(n / 10);
if (_s) n -= _s * 10;
const _g = n;
if (_b) _r += ` ${digit[_b]} Hundred`;
if (_s === 1) {
_r += ` ${digit3[_s * 10 + _g]}`
} else {
if (_s) _r += ` ${digit2[_s]}`;
if (_g) _r += ` ${digit[_g]}`;
}
return _r;
}
let rst = "";
for (let i in red) {
if (red[i] !== 0) {
if (i == -1) {
rst += getString(red[i]);
} else {
rst += getString(red[i]) + " " + i;
}
}
}
return rst.trim();
};
我的思路:
- 发音规则是三个数组为一组,最后加上单位即可
- 注意最大数范围是 2^31 - 1 在 Billion 范围之内
[] hundred [] [单位]- 首先将数组分成三个一组
得分结果: 12.50% 56.25%
总结提升:
- Ten 是易错点。
- 用下面的方法取出数位数字来:
const b = Math.floor(num / 10 ** 9);
if (b) num -= b * 10 ** 9;
const m = Math.floor((num) / 10 ** 6);
if (m) num -= m * 10 ** 6;
const t = Math.floor((num) / 10 ** 3);
if (t) num -= t * 10 ** 3;
const g = num;
2.2 [165] 比较版本号
给你两个 版本号字符串 version1 和 version2 ,请你比较它们。版本号由被点 '.' 分开的修订号组成。修订号的值 是它 转换为整数 并忽略前导零。
比较版本号时,请按 从左到右的顺序 依次比较它们的修订号。如果其中一个版本字符串的修订号较少,则将缺失的修订号视为 0。
返回规则如下:
如果 version1 < version2 返回 -1,
如果 version1 > version2 返回 1,
除此之外返回 0。
示例 1:
输入:version1 = "1.2", version2 = "1.10"
输出:-1
解释:
version1 的第二个修订号为 "2",version2 的第二个修订号为 "10":2 < 10,所以 version1 < version2。
示例 2:
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:
忽略前导零,"01" 和 "001" 都代表相同的整数 "1"。
示例 3:
输入:version1 = "1.0", version2 = "1.0.0.0"
输出:0
解释:
version1 有更少的修订号,每个缺失的修订号按 "0" 处理。
提示:
1 <= version1.length, version2.length <= 500
version1 和 version2 仅包含数字和 '.'
version1 和 version2 都是 有效版本号
version1 和 version2 的所有修订号都可以存储在 32 位整数 中
尝试完成:
/**
* @param {string} version1
* @param {string} version2
* @return {number}
*/
var compareVersion = function(version1, version2) {
const arr1 = version1.split(".").map(v=>parseInt(v));
const arr2 = version2.split(".").map(v=>parseInt(v));
const n = Math.max(arr1.length,arr2.length);
for(let i = 0; i < n; i++) {
const _a = arr1[i] ?? 0;
const _b = arr2[i] ?? 0;
if(_a>_b) return 1;
if(_a<_b) return -1;
}
return 0;
};
我的思路:
使用 dot 作为分隔符化成数组,然后逐个比较,如果是 undefined 则当成 0 对待即可。
得分结果: 78.50% 8.83%
3. 六个 vue2.x 面试题
- vue2.0组件通信⽅式有哪些?
-
⽗⼦组件通信:
- props 和 event、v-model、 .sync、 ref、 children
-
非⽗⼦组件通信:
- listeners、 provide 和 inject、eventbus、通过根实例$root访问、vuex、dispatch 和 brodcast
- v-model是如何实现双向绑定的?
-
vue 2.0
- v-model 是⽤来在表单控件或者组件上创建双向绑定的,他的本质是 v-bind 和 v-on 的语法糖,在⼀个组件上使⽤ v-model ,默认会为组件绑定名为 value 的 prop 和名为 input 的事件。
-
Vue3.0
- 在 3.x 中,⾃定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件
- Vuex和单纯的全局对象有什么区别?
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发⽣变化,那么相应的组件也会相应地得到⾼效更新。
- 不能直接改变 store 中的状态。改变 store 中的状态的唯⼀途径就是显式地提交 (commit)mutation。这样使得我们可以⽅便地跟踪每⼀个状态的变化,从⽽让我们能够实现⼀些⼯具帮助我们更好地了解我们的应⽤。
- Vue 的⽗组件和⼦组件⽣命周期钩⼦执⾏顺序是什么?
-
渲染过程:
- ⽗组件挂载完成⼀定是等⼦组件都挂载完成后,才算是⽗组件挂载完,所以⽗组件的mounted在⼦组件mouted之后
- ⽗beforeCreate -> ⽗created -> ⽗beforeMount -> ⼦beforeCreate -> ⼦created -> ⼦beforeMount -> ⼦mounted -> ⽗mounted
-
⼦组件更新过程:
- 影响到⽗组件: ⽗beforeUpdate -> ⼦beforeUpdate->⼦updated -> ⽗updted
- 不影响⽗组件: ⼦beforeUpdate -> ⼦updated
-
⽗组件更新过程:
- 影响到⼦组件: ⽗beforeUpdate -> ⼦beforeUpdate->⼦updated -> ⽗updted
- 不影响⼦组件: ⽗beforeUpdate -> ⽗updated
-
销毁过程:
- ⽗beforeDestroy -> ⼦beforeDestroy -> ⼦destroyed -> ⽗destroyed
看起来很多好像很难记忆,其实只要理解了,不管是哪种情况,都⼀定是⽗组件等待⼦组件完成后,才会执⾏⾃⼰对应完成的钩⼦,就可以很容易记住。
- v-show 和 v-if 有哪些区别?
- v-if 会在切换过程中对条件块的事件监听器和⼦组件进⾏销毁和重建,如果初始条件是false,则什么都不做,直到条件第⼀次为true时才开始渲染模块。
- v-show 只是基于css进⾏切换,不管初始条件是什么,都会渲染。
所以, v-if 切换的开销更⼤,⽽ v-show 初始化渲染开销更⼤,在需要频繁切换,或者切换的部分dom很复杂时,使⽤ v-show 更合适。渲染后很少切换的则使⽤ v-if 更合适。
- Vue 中 v-html 会导致什么问题 在⽹站上动态渲染任意 HTML,很容易导致 XSS 攻击。所以只能在可信内容上使⽤ v-html,且永远不能⽤于⽤户提交的内容上。
4. 五句英语积累
- At what time? 几点钟
- Boarding is at gate 23. 登机口在 23 号
- Can you help me?
- Does this role go to Paris? 这是去巴黎的路吗
- Do you know what this says? 你知道这是什么意思吗?