• 作者:老汪软件技巧
  • 发表时间:2024-09-16 15:02
  • 浏览量:

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

1. 一个类型体操

类型体操题目集合OmitByType

从 T 中选出一组无法分配给 U 的属性

示例:

type OmitBoolean = OmitByType<
  {
    name: string;
    count: number;
    isReadonly: boolean;
    isEnable: boolean;
  },
  boolean
>; // { name: string; count: number }

分析

这个是对 Object 进行遍历。然后在遍历的时候使用 as 对得到的结果进行限制。

尝试写出

type OmitByTypeextends {}, K> = {
  [P in keyof T as T[P] extends K ? P : never]: T[P];
};

测试用例

type OmitBoolean = OmitByType<
  {
    name: string;
    count: number;
    isReadonly: boolean;
    isEnable: boolean;
  },
  boolean
>; // { name: string; count: number }

参考答案

type OmitByType = { [K in keyof T as T[K] extends U ? never : K]: T[K] };

经验总结

in keyof as 的搭配。

另外一个类型体操

类型体操题目集合

Mutable

实现一个通用的类型 Mutable,使类型 T 的全部属性可变(非只读)。

interface Todo {
  readonly title: string;
  readonly description: string;
  readonly completed: boolean;
}
type MutableTodo = Mutable<Todo>; // { title: string; description: string; completed: boolean; }

分析

在 Ts 中,我们可以使用 -readonly 来消除原来的只读属性。所以这本质上还是对 object 的遍历。

尝试写出

type Mutableextends {}> = {
  -readonly [K in keyof T]: T[K];
};

测试用例

type MutableTodo = Mutable<Todo>; // { title: string; description: string; completed: boolean; }

参考答案

type Mutableextends object> = { -readonly [K in keyof T]: T[K] };

经验总结-readonly2. 两个 Leetcode 题目

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

2.1 [522] 最长特殊序列 Ⅱ

给定字符串列表 strs ,返回其中 最长的特殊序列 的长度。如果最长特殊序列不存在,返回 -1 。
特殊序列 定义如下:该序列为某字符串 独有的子序列(即不能是其他字符串的子序列)。
 s 的 子序列可以通过删去字符串 s 中的某些字符实现。
例如,"abc" 是 "aebdc" 的子序列,因为您可以删除"aebdc"中的下划线字符来得到 "abc" 。"aebdc"的子序列还包括"aebdc"、 "aeb" 和 "" (空字符串)。
示例 1:
输入: strs = ["aba","cdc","eae"]
输出: 3
示例 2:
输入: strs = ["aaa","aaa","aa"]
输出: -1
提示:
2 <= strs.length <= 50
1 <= strs[i].length <= 10
strs[i] 只包含小写英文字母

尝试实现:

/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function (s, t) {
  let sIndex = 0;
  let tIndex = 0;
  while (sIndex < s.length && tIndex < t.length) {
    if (s[sIndex] === t[tIndex]) {
      sIndex++;
    }
    tIndex++;
  }
  return sIndex === s.length;
};
/**
 * @param {string[]} strs
 * @return {number}
 */
var findLUSlength = function (strs) {
  for (let i = 0; i < strs.length - 1; i++) {
    const a = strs[i];
    for (let j = i + 1; j < strs.length; j++) {
      const b = strs[j];
      if (a === b) {
        strs[i] = "";
        strs[j] = "";
        continue;
      }
      if (isSubsequence(a, b)) {
        strs[i] = "";
      }
      if (isSubsequence(b, a)) {
        strs[j] = "";
      }
    }
  }
  let max = 0;
  strs.forEach((v) => {
    max = Math.max(max, v.length);
  });
  return max === 0 ? -1 : max;
};

每日积累知识的好处_每日积累知识的古文_

我的思路:

得分结果: 21.62% 16.54%

总结提升:

使用双指针方法来提升效率。2.2 [66] 加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
示例 3:
输入:digits = [0]
输出:[1]
提示:
1 <= digits.length <= 100
0 <= digits[i] <= 9

尝试完成:

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function (digits) {
  function exec(_d, _p) {
    if (_p === -1) {
      _d.unshift(1);
      return;
    }
    if (_d[_p] === 9) {
      _d[_p] = 0;
      exec(_d, _p - 1);
      return;
    }
    _d[_p] = _d[_p] + 1;
    return;
  }
  const n = digits.length;
  exec(digits, n - 1);
  return digits;
};

我的思路:额外考虑进位以及类似 [9,9,9] 这种会增加最前面 1 的情况。

得分结果: 29.42% 41.65%

2.3 [67] 二进制求和

给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。
示例 1:
输入:a = "11", b = "1"
输出:"100"
示例 2:
输入:a = "1010", b = "1011"
输出:"10101"
提示:
1 <= a.length, b.length <= 104
a 和 b 仅由字符 '0' 或 '1' 组成
字符串如果不是 "0" ,就不含前导零

尝试完成:

/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
var addBinary = function (a, b) {
  return (BigInt("0b" + a) + BigInt("0b" + b)).toString(2);
};

我的思路:

在 js 中字符串数字减 0 变成数字这一点对于各个进制都是成立的。使用 toString 将数字变成字符串可以是常见的任意进制。BigInt('0b10') 是可行的,并且没有精度损失。

得分结果: 10.89% 90.85%

总结提升:

'0b11100' - 0 这种操作在数字很大的时候会产生误差,所以使用 BigInt('0b11100') 替换。BigInt 的包装类型也可以使用 toString 方法转成任意进制的字符串并且没有损失。2.4 [415] 字符串相加

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
示例 1:
输入:num1 = "11", num2 = "123"
输出:"134"
示例 2:
输入:num1 = "456", num2 = "77"
输出:"533"
示例 3:
输入:num1 = "0", num2 = "0"
输出:"0"
提示:
1 <= num1.length, num2.length <= 104
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零

尝试完成:

/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var addStrings = function (num1, num2) {
  const rst = [];
  const n1 = num1.split("");
  const n2 = num2.split("");
  const c1 = n1.length;
  const c2 = n2.length;
  function add(rst, i, p) {
    console.log(i);
    const _a = (n1[c1 - 1 - i] ?? "0") - 0 + ((n2[c2 - 1 - i] ?? "0") - 0) + p;
    rst.unshift(_a % 10);
    if (i === Math.max(c1, c2)) return;
    if (_a > 9) {
      add(rst, i + 1, 1);
    } else {
      if (i === Math.max(c1, c2) - 1) return;
      add(rst, i + 1, 0);
    }
    return;
  }
  add(rst, 0, 0);
  return rst.join("");
};

我的思路:

那就不写这样的答案了 BigInt(num1)+BigInt(num2) + ""构造递归定点更新辅助函数 add 处理好边界条件即可。

总结提升:这是 AI 答案:

/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var addStrings = function (num1, num2) {
  const n1 = num1.split("");
  const n2 = num2.split("");
  const maxLength = Math.max(n1.length, n2.length);
  let carry = 0;
  let result = [];
  for (let i = 0; i < maxLength || carry; i++) {
    const digit1 = i < n1.length ? parseInt(n1[n1.length - 1 - i], 10) : 0;
    const digit2 = i < n2.length ? parseInt(n2[n2.length - 1 - i], 10) : 0;
    const sum = digit1 + digit2 + carry;
    result.unshift(sum % 10);
    carry = Math.floor(sum / 10);
  }
  return result.join("");
};

得分结果: 5.07% 5.07%

3. 六个 vue2.x 面试题vue 中组件 data 为什么是 return ⼀个对象的函数,⽽不是直接是个对象?

如果将 data 定义为对象,这就表示所有的组件实例共⽤了⼀份 data 数据,因此,⽆论在哪个组件实例中修改了 data,都会影响到所有的组件实例。

组件中的 data 写成⼀个函数,数据以函数返回值形式定义,这样每复⽤⼀次组件,就会返回⼀份新的 data,类似于给每个组件实例创建⼀个私有的数据空间,让各个组件实例维护各⾃的数据。⽽单纯的写成对象形式,就使得所有组件实例共⽤了⼀份 data,就会造成⼀个变了全都会变的结果。

Vue 中的 computed 是如何实现的流程总结如下:Vue 的响应式原理Object.defineProperty 有哪些缺点?

Vue2.0 中如何检测数组变化?Vue 的 Observer 对数组做了单独的处理,对数组的⽅法进⾏编译,并赋值给数组属性的 proto属性上,因为原型链的机制,找到对应的⽅法就不会继续往上找了。编译⽅法中会对⼀些会增加索引的⽅法( push , unshift , splice )进⾏⼿动 observe。

nextTick 是做什么⽤的,其原理是什么?

能回答清楚这道问题的前提,是清楚 EventLoop 过程。

在下次 DOM 更新循环结束后执⾏延迟回调,在修改数据之后⽴即使⽤ nextTick 来获取更新后的 DOM。nextTick 对于 micro task 的实现,会先检测是否⽀持 Promise ,不⽀持的话,直接指向 macrotask,⽽ macro task 的实现,优先检测是否⽀持 setImmediate (⾼版本 IE 和 Etage ⽀持),不⽀持的再去检测是否⽀持 MessageChannel,如果仍不⽀持,最终降级为 setTimeout 0;默认的情况,会先以 micro task ⽅式执⾏,因为 micro task 可以在⼀次 tick 中全部执⾏完毕,在⼀些有重绘和动画的场景有更好的性能。但是由于 micro task 优先级较⾼,在某些情况下,可能会在事件冒泡过程中触发,导致⼀些问题,所以有些地⽅会强制使⽤ macro task (如 v-on )。

注意:之所以将 nextTick 的回调函数放⼊到数组中⼀次性执⾏,⽽不是直接在 nextTick 中执⾏回调函数,是为了保证在同⼀个 tick 内多次执⾏了 nextTcik ,不会开启多个异步任务,⽽是把这些异步任务都压成⼀个同步任务,在下⼀个 tick 内执⾏完毕。

4. 五句英语积累How do I get to the American Embassy? 我怎么去美国领事馆How long does it take by car?How long does it take to get to Shanghai?How long is the flight?I want to ask you a question.