每日知识积累 Day 16

211 阅读6分钟

每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。

1. 一个类型体操

类型体操题目集合

Length of String

计算字符串的长度,类似于 String#length

分析

这个仍然是递归,通过这道题目我想出来了将 String 转成 Tuple 的方法。

尝试写出

type LengthOfString<T extends string, R extends any[] = []> = {
  0: R["length"];
  1: T extends `${infer F}${infer L}`
    ? LengthOfString<L, [unknown, ...R]>
    : LengthOfString<"", [unknown, ...R]>;
}[T extends "" ? 0 : 1];

测试用例

type C = LengthOfString<"1abc12312312">; // type C = 10

参考答案

type LengthOfString<
  S extends string,
  U extends any[] = []
> = S extends `${infer L}${infer R}`
  ? LengthOfString<R, [L, ...U]>
  : U["length"];

经验总结

  1. 比起三元表达式 {}[] 这种形式更加容易理解。
  2. 将 String 转成 Tuple 的方法:
type String2Tuple<T extends string, R extends string[] = []> = {
  "0": [...R];
  "1": T extends `${infer F}${infer L}` ? String2Tuple<L, [...R, F]> : never;
}[T extends `${infer F}${infer L}` ? "1" : "0"];

type C = String2Tuple<"1231231">; // type C = ["1", "2", "3", "1", "2", "3", "1"]

2. 两个 Leetcode 题目

刷题的顺序参考这篇文章 LeeCode 刷题顺序

2.1 [14] 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""。


示例 1:

输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:

输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。


提示:

1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成

尝试实现:

/**
 * @param {string[]} strs
 * @return {string}
 */
var longestCommonPrefix = function (strs) {
  const n = strs.length;
  let rst = "";
  let idx = 0;
  while (true) {
    let _a = strs[0][idx];
    for (let i = 0; i < n; i++) {
      if (strs[i][idx]) {
        if (strs[i][idx] !== _a) {
          return rst;
        }
      } else {
        return rst;
      }
    }

    rst += _a;
    idx++;
  }
};

我的思路:

  1. 初始值为 "" 直接返回
  2. 使用 while 循环而不是 for 循环

得分结果: 35.40% 64.19%

总结提升:

  1. 时时刻刻记住防错和出口。

2.2 [434] 字符串中的单词数

统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。

请注意,你可以假定字符串里不包括任何不可打印的字符。

示例:

输入: "Hello, my name is John"
输出: 5
解释: 这里的单词是指连续的不是空格的字符,所以 "Hello," 算作 1 个单词。

尝试完成:

/**
 * @param {string} s
 * @return {number}
 */
var countSegments = function (s) {
  let rst = 0;
  s.split(" ").forEach((v) => {
    if (v !== "") rst++;
  });
  return rst;
};

我的思路:

  1. 感觉没啥好说的,注意 js 语言本身的限制。

得分结果: 39.83% 36.44%

总结提升:

  1. 收集一些之前没有注意到的语法限制:
"".split(" "); // [''] length = 1

"   ".split(" "); // ['', '', '', ''] length = 3

2.3 [58] 最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。


示例 1:

输入:s = "Hello World"
输出:5
解释:最后一个单词是“World”,长度为 5。
示例 2:

输入:s = "   fly me   to   the moon  "
输出:4
解释:最后一个单词是“moon”,长度为 4。
示例 3:

输入:s = "luffy is still joyboy"
输出:6
解释:最后一个单词是长度为 6 的“joyboy”。


提示:

1 <= s.length <= 104
s 仅有英文字母和空格 ' ' 组成
s 中至少存在一个单词

尝试完成:

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLastWord = function (s) {
  const _s = s.trim();
  const _a = _s.split(" ");
  const n = _a.length;
  if (!n) return 0;

  return _a[n - 1].length;
};

我的思路:

  1. 感觉没啥好说的,注意 js 语言本身的限制。

得分结果: 99.70% 80.90%

2.4 [344] 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。



示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]


提示:

1 <= s.length <= 105
s[i] 都是 ASCII 码表中的可打印字符

尝试完成:

/**
 * @param {character[]} s
 * @return {void} Do not return anything, modify s in-place instead.
 */
var reverseString = function (s) {
  let i = 0;
  let j = s.length - 1;
  while (j > i) {
    [s[i], s[j]] = [s[j], s[i]];
    j--;
    i++;
  }
  return s;
};

我的思路:

  1. [a,b]=[b,a] 这种交换对于数组中的元素也是可以的!

得分结果: 13.33% 41.92%

3. 三个前端题目

  1. 延时加载 js 脚本的几种方法
  • defer 属性:在 script 标签上写入 defer 标志,此脚本虽然会在文档解析的同时进行加载,但会等待文档解析完成之后才会执行。多个带 defer 标签的 script 标签都会在文档加载完毕之后按照在 dom 文档中的顺序从上到下依次执行,是有序的
  • async 属性:在 script 标签上写入 async 标志,此脚本会在文档解析的同时进行加载,并且一旦加载完成就立即开始执行,同时阻塞文档的解析。多个带有 async 标志的 script 标签,因为网络原因加载时间是不能保证的,所以它们之间的执行顺序是不能够得到保证的
  • 动态标签:在某个时机(通常是某个事件发生之后),动态的创建 script 标签并设置其 scr 的值,设置之后浏览器会自动发送 get 请求获取对应的资源;加载完成并执行完毕之后会触发 script 标签对象的 onload 事件;加载失败执行的是 onerror 事件;
  • 异步执行:将一些脚本代码放到 setTimeout 的定时器中执行;
  • 调整位置:调整 script 标签在 dom 中的位置也可以改变 script 的执行时间。
  1. 类数组以及其转成数组的方法
  • 类数组的本质是一个 js 中的对象;此对象有length 属性和一些索引属性,所谓的索引属性指的就是 number 类型的 propertykey。但是如果使用Array.isArray来检验会得到 false 结果;
  • 常见的类数组包括普通函数的arguments对象,以及使用 querySelector 获取的NodeList对象;
  • 将类数组 x 变成真实数组的方法有:
    • Array.from(x)
    • [...x]
    • 此外,可以借助 Array.prototype 上的各类方法;这种方式的原理就是:由于 x 不是数组,但是可以假装 x 是数组,这样 x 就可以使用数组上的特定方法,这些特定的方法执行结果是数组;那么如何假装 x 是真的数组呢?使用Array.prototype.fnc就可以了,而数组构造函数原型对象上能够返回数组的方法有:
      • slice
      • call
      • concat
      • forEach

于是:

  • Array.prototype.slice.call(x)
  • Array.prototype.call.call(x,0)
  • Array.prototype.concat.call(x,[])
  • Array.prototype.forEach.call(x,v=>v)
  • ...
  1. arguments 的理解以及其遍历的方式
  • 首先,arguments 是类数组,原因是它具有 length 属性(表示实参/实际收到的的数目)和索引属性(表示实参序列)。但是无法通过 Array.isArray 的检测,并且不具有大部分数组方法。
  • 然后,arguments 对象只存在于普通对象中,而在箭头函数中不存在;
  • 此外,arguments 对象上的 callee 属性表示正在运行的函数,可以用做匿名函数的递归;
  • 最后,遍历类数组的方法大致可以分成两种,一个是转换为真正的数组,然后遍历,另外一个是嫁接数组上的方法进行遍历
  • 具体来说:Array.prototype.forEach.call(likeArray, cb)

4.四句英语积累

  1. be done -- I guess we [are done].
  2. lemme -- [Lemme] go and pray fast.
  3. dare -- You don't want or you don't [dare]?
  4. updated -- to keep myself [updated].