每日知识积累 Day 7

136 阅读6分钟

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

1. 一个类型体操

类型体操题目集合

Pick

题目要求

实现 TS 内置的 Pick<T, K>,但不可以使用它。

从类型 T 中选择出属性 K,构造成一个新的类型。

例如:

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = MyPick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

分析

  1. 首先必须搞清楚如何遍历 Ts 中的 object 类型,只有这样才能在遍历的时候做手脚。
type LookObj<T extends {}> = {
  [K in keyof T]: T[K];
};

type Obj = {
  name: string;
  age: number;
};

type C = LookObj<Obj>;
  1. 然后就是知道如何遍历联合类型。
// 由于 T extends {} 所以 keyof T 是联合类型,所以遍历联合类型使用 in 关键字
type LookObj<T extends {}> = {
  [K in keyof T]: T[K];
};
  1. 最后是知道如何求两个联合类型的交集。
type Inter<T, P> = T extends P ? T : never;
type C = Inter<"a" | "b" | "c", "d" | "a">; // "a"

有了上面的基础信息,那么我们写 MyPick 的时候,就是先求出两个泛型的交集,然后遍历交集构造新的 object 类型即可。

尝试写出

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

测试用例

type TodoPreview = MyPick<Todo, "title" | "completed">;
/**
 * type TodoPreview = {
 *   title: string;
 *   completed: boolean;
 * }
 */

参考答案

type MyPick<T extends {}, K extends keyof T> = { [P in K]: T[P] };

经验总结

虽然没有我考虑的完全,但是这里的 K extends keyof T 还是值得一学的。

2. 两个 Leetcode 题目

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

2.1 [283] 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。


示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]


提示:

1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1


进阶:你能尽量减少完成的操作次数吗?

尝试实现:

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function (nums) {
  let count = 0;
  const n = nums.length;
  if (n < 2) return nums;

  for (let i = 0; i < n; i++) {
    const num = nums[i];

    nums[i - count] = num;

    if (num === 0) {
      count++;
    }
  }

  for (let i = n - count; i < n; i++) {
    nums[i] = 0;
  }

  return nums;
};

我的思路:

  1. 分为两步,第一步是遍历数组,对于每一个元素,假如前面有 count 个 0 则往前移动 count 位;同时,如果自己也是 0 则 count + 1
  2. 第二步,得到第一次遍历之后的数据之后,最后的 count 个元素都应该置为 0

得分结果: 19.75% 18.31%

2.2 [118] 杨辉三角

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。


示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:

输入: numRows = 1
输出: [[1]]


提示:

1 <= numRows <= 30

尝试完成:

/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function (numRows) {
  const rst = [];

  for (let i = 0; i < numRows; i++) {
    if (i === 0) rst.push([1]);
    if (i === 1) rst.push([1, 1]);
    if (i === 2) rst.push([1, 2, 1]);
    if (i > 2) {
      const current = [];

      for (let j = 0; j < i + 1; j++) {
        if (j === 0 || j === i) {
          current[j] = 1;
        } else {
          current[j] = rst[i - 1][j - 1] + rst[i - 1][j];
        }
      }

      rst.push(current);
    }
  }

  return rst;
};

我的思路:

  1. 先写边界条件下的返回值,边界条件一般也是初始条件。
  2. 观察到 numRows 为行数,并且每一行的元素个数等于行序号 + 1

得分结果: 9.14% 15.33%

总结提升:

  1. 序列号是从 0 开始的,但是自然数是从 1 开始的;这很难办,很容易出错,需要想一个好用的解决方案。

3. 三个前端题目

  1. 手写一个函数,实现简单浅拷贝。

这个题要想做的好,首先必须画好边界:

  • 只拷贝对象类型,其他基本类型直接照原样输出;
  • 简单的浅拷贝只需要区分数组和一般值对象,不要去管其他类型的对象;
  • 只对目标对象上的 ownProperty 进行拷贝,而不要管原型链上的属性;

据此,实现的程序如下:

function shallowCopy(source) {
  if (Object(source) !== source) return source;
  const _tmp = Array.isArray(source) ? [] : {};
  for (let key in source) {
    if (source.hasOwnProperty(key)) _tmp[key] = source[key];
  }
  return _tmp;
}

补充一个知识点,如果 a 是"object"类型的,如何判断 a 的 b 属性的值是否是真的 undefined?

() => a.b === undefined && a.hasOwnProperty(b);
  1. 叙述 js 中其他类型的值到字符串类型的转换规则 将 x 转成 string 类型,可分为隐式转换和强制转换,所谓隐式转换就是指""+x的结果,所谓强制转换指的就是String(x)的结果:
  • undefined: "undefined"
  • null: "null"
  • string: 返回原值,注意 String("abc")不是包装类型,注意与 new String("abc")区别开
  • number: "Infinity", "-Infinity", "NaN", 变成相同的字面量,但是数字太大会变成字符串类型的科学计数法的模样
  • boolean: "true", "false"
  • symbol: 隐式转换报错;强制转换如:String(Symbol(9)) // "Symbol(9)"
  • bigint: 会变成将 n 去掉的相同的字面量,不会出现因为数字大变成字面量
  • object: 内部机制

所谓内部机制,指的就是:

1). 判断 x 是否有 valueOf 方法,如果有的话根据其返回值做出不同的反映:

  • 返回值为 symbol 类型,报错
  • 返回值为引用类型, 跳到 2
  • 返回值 y 为其它非引用类型,最终结果为:String(y) 2). 如果没有 valueOf 方法,或 valueOf 返回引用类型,则接着判断是否有 toString 方法,如果有的话根据其返回值做出不同的反映:
  • 返回值为 symbol 类型,报错
  • 返回值为引用类型,报错
  • 返回值 y 为其它非引用类型的 y,最终结果为:String(y) 3). 如果没有 toString 方法,则返回Object.prototype.toString,call(x)
  1. 叙述 js 中其他类型的值到布尔值的转换规则 js 中其他类型的值向布尔类型转换就比较简单了;事实上,js 中常见的一共只有8假值,也就是不能通过if()判断的值:
    1. ""
    1. false
    1. +0
    1. -0
    1. NaN
    1. null
    1. undefined
    1. Boolean(false)

其余的值都能够通过if()的判断,也就是所谓的真值。下面是一些容易搞混的假值:

    1. ![]
    1. !({})
    1. !123n
    1. !Symbol(0)
    1. !Object(false)

4.四句英语积累

  1. deal with something
    • I had to deal with a lot of [customer complaints] last week.
    • Weve had some technical problems with the new websites, but [we're dealing with them].
  2. set up -- to organise or plan something such as a meeting or event
    • Can you s[et up a meeting for Thursday afternoon] please?
    • We've [set up a series of workshops] to train staff on how to use the new software.