字节跳动面试记录(三)

一面

【框架相关】

React 什么时候有的 hook,为什么会有 hook 这种写法?

On February 16, 2019, React 16.8 was released to the public

Intro video

官方文档介绍 React Hook 产生的动机:

  • 组件之间的逻辑状态很难复用
    • 问题:React 不提供将可复用的行为『附加』到组件的方法,那么用户可能会采用 render props 为子组件传入一个渲染函数,或者使用 高阶组件 来实现复用逻辑。但这些方式可能需要在是用的时候去改造你的原始组件,这很麻烦。并且在调试工具中,会发现组件会被层层包裹着各种逻辑(providers, consumers, higher-order components, render props, and other abstractions),这种现象被称为『Wrapper Hell』。
    • 解决:使用 Hooks,您可以从组件中提取有状态逻辑,以便可以对其进行独立测试和重用。Hooks 允许您重用有状态逻辑,而无需更改组件层次结构。这使得在许多组件之间或与社区共享 Hooks 变得容易。
  • 复杂的组件将被难以理解
    • 问题:我们经常不得不维护最初很简单的组件,但后来却变成了一堆难以管理的状态逻辑和副作用。每个生命周期方法通常包含不相关逻辑,这使得很容易引入错误和不一致。在许多情况下,不可能将这些组件分解为更小的组件,因为状态逻辑无处不在,这是许多人更喜欢将 React 与单独的状态管理库结合起来的原因之一。
    • 解决:为了解决这个问题,Hooks 允许您根据相关部分(例如设置订阅或获取数据)将一个组件拆分为更小的函数(使用 effect hook),而不是根据生命周期方法强制拆分。您还可以选择使用 reducer 来管理组件的本地状态,以使其更具可预测性。
  • Class Component 会让人类和机器都感到迷惑
    • 问题:Class 会让开发者有学习成本,必须了解 JavaScript 中面向对象的工作模式,并且它与其他的大多数语言的工作方式有很大区别。还必须理解 this 绑定。如果没有 ES2022 的 public 定义,代码会非常冗长。人们可以很好的理解 props、state 的上下数据流,但仍然难以解读类。 React 中函数组件和类组件之间的区别以及何时使用它们甚至在经验丰富的 React 开发人员之间也会产生分歧。此外,类组件在提前编译(ahead-of-time compilation)上有一些问题,可能会鼓励无意义的模式,使这些优化回落到较慢的路径。类也给当今的工具带来了问题。例如,类不能很好地缩小,并且它们使热重载不稳定且不可靠。我们希望提供一个 API,使代码更有可能保持在可优化的路径上。
    • 解决:为了解决这些问题,Hooks 让您无需类即可使用更多 React 功能。从概念上讲,React 组件一直更接近于函数。 Hooks 拥抱函数,​​但又不牺牲 React 的实用精神。 Hooks 提供了对命令式逃生口的访问,并且不需要您学习复杂的函数式或反应式编程技术。

React hook 相对于之前的 Class Component 有什么优势?

上个问题中已经提到了。

再补充一个 Pure Component:如果 props 没有被改变就不会重新渲染的组件,文档,但 Pure Component 属于类组件,如果使用 React hook 则可以关注 memo

如果使用 setState 设置一个与旧值相同的新值,组件还会重新渲染吗?

会重新渲染,因为 React 只有在执行 render 函数后才知道本次更新是否 bail out,但是不会 depp render 子节点*。

useReduce 会用吗,有什么作用?

使用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function reduer() {
let newState = JSON.parse(JSON.stringify(state));
switch (action) {
case "add":
newState.count = state.count + 1;
return newState;
case "sub":
newState.count = state.count - 1;
return newState;
default:
return state;
}

const defaultState = {
count: 0
};

const [state, dispatch] = useReducer(reduer, defaultState);

dispatch("add")

函数返回一个当前变量的状态(state)与一个调度函数(dispatch)来变更状态值。

注意 :在 reducer 函数中 state 是只读的,不要对其进行修改,而是返回一个新的 state

相比于 useState 的优势:

  • 适用于处理复杂的状态逻辑:如果状态之间存在复杂的依赖关系、或需要进行复杂的计算和变换,useReducer 提供了一种更好的方式来管理和更新状态。
  • 可以更好地组织代码:使用 useReducer 可以将相关的状态和操作集中在一起,使代码更具可读性和可维护性。
  • 可以与 useContext 一起使用:useReducer 的状态可以作为 useContext 的值传递给其他组件,以便在整个应用程序中共享状态。

综上所述,当需要处理复杂的状态逻辑时,或需要更好地组织代码结构时,可以考虑使用 useReducer 替代 useState。

Vue3 与 Vue2 区别

参考

Vue 的优化手段

官方指南:

  • 页面加载优化:
    • 使用 SSR
    • 使用 Tree shaking
    • 合理的代码分割
  • 更新优化:
    • 一个子组件只会在其至少一个 props 改变时才会更新,所以要尽量减少 Props 的更新
    • 使用 v-once 让组件只渲染一次并跳过后续的渲染
    • 使用 v-memo 传入一个依赖数组,只有依赖数组的值发生变化后,组件才会被重新渲染,通常与 v-for 一起使用来优化渲染列表
    • 计算属性稳定性(3.4+),返回相同的计算值才会发生副作用,但如果计算值是一个 Object,则计算值相同时最好返回原对象,如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
       const computedObj = computed((oldValue) => {
      const newValue = {
      isEven: count.value % 2 === 0
      }
      if (oldValue && oldValue.isEven === newValue.isEven) {
      return oldValue
      }
      return newValue
      })
  • 通用优化:
    • 使用大虚拟列表
    • 减少大型不可变数据的响应性开销,如使用 shallowRefshallowReactive
    • 避免不必要的组件抽象:组件示例比普通的 DOM 节点昂贵得多,尤其是要注意在大型列表中组件之间的嵌套

Vue 中如果不想让一部分数据有响应式,从而来节省性能,该怎么做?

Vue3:使用 [shallowRef](https://cn.vuejs.org/api/reactivity-advanced.html#shallowref)

Vue2:
- 不要定义到 data 的返回值上,直接定义到组件 this 上,更新数据口使用 $forceUpdate() 手动更新视图
- 使用 Object.preventExtensions 创建的对象只有浅响应式
- 更多参考:为Vue组件添加非响应式数据

【网络相关】

为什么会有 http2,相对于 http1 的优势是什么

HTTP 的第一个版本诞生于 1997 年,由于它经历了多个开发阶段,因此 HTTP 的第一个版本称为 HTTP/1.1,此版本仍在网络上使用。2015年,一个名为HTTP/2的新版本被创建。 HTTP/2解决了HTTP/1.1的创造者们没有预料到的几个问题。 特别是,HTTP/2比HTTP/1.1更快、更有效。 HTTP/2更快的方式之一是它在加载过程中如何优先处理内容。

影响性能的区别:

  • 多路复用: HTTP/1.1 依次加载各个资源,因此,如果无法加载某一资源,它将阻碍其后的所有其他资源。相比之下,HTTP/2 可以使用单个 TCP 连接来一次发送多个数据流,使得任何资源都不会会阻碍其他资源。为此,HTTP/2 将数据拆分为二进制代码消息并为这些消息编号,以便客户端知道每个二进制消息所属的流。同时 HTTP/2 支持按权重进行优先加载,但是需要要求浏览器和服务端都支持,高级的浏览器会有自己的优先策略。
  • 服务器推送: 通常,服务器仅在客户端要求时才向客户端设备提供内容。但是,这种方法并不总是适用于现代网页,因为现代网页通常涉及客户端必须请求的数十个独立资源。HTTP/2 通过允许服务器在客户端请求之前向客户端“推送”内容来解决此问题。服务器还发送一条消息,让客户知道预期推送的内容是什么,就像 Bob 在发送整本书之前向 Alice 发送小说目录一样。
  • 标头压缩: 小文件的加载速度比大文件快。为了提高 Web 性能,HTTP/1.1 和 HTTP/2 都会压缩 HTTP 消息以使其更小。但是,HTTP/2 使用一种称为 HPACK 的更高级压缩方法,可以消除 HTTP 标头数据包中的多余信息。这样可以从每个 HTTP 数据包中消除几个字节。考虑到即使只加载一个网页时所涉及的 HTTP 数据包的数量,这些字节会迅速累加,从而加快了加载速度。

Http2 实现多路复用、服务端推送的细节

【项目相关】

讲一下你做的项目的优化手段

为什么要用 SSR,解决了什么痛点

国际化的项目有什么难点

【笔试】

说一下节流与防抖的应用场景,对其进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 防抖
function debounce(fn, time) {
let timer = null;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.call(this, ...args);
timer = null;
}, time);
};
}

// 节流
function throttle(fn, time) {
let timer = null;
return function (...args) {
if (!timer) {
fn.call(this, ...args);
timer = setTimeout(() => {
timer = null;
}, time);
}
};
}

实现 add(1)(2)(3)()

1
2
3
4
5
6
7
8
9
10
11
12
13
function add(num) {
let sum = num || 0;
function innerFunc(subNum) {
if (typeof subNum === "number") {
sum += subNum;
return innerFunc;
}
return sum;
}
return innerFunc;
}

console.log(add(1)(2)(3)(4)());

实现 instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function instOf(inst, cons) {
let parent = inst?.__proto__;
if (!parent) {
return false;
}
let end = false;
while (parent && !end) {
if (parent === cons?.prototype) {
end = true;
} else {
parent = parent?.__proto__;
}
}
return end;
}

class Animal {
constructor() {}
}

class Cat extends Animal {
constructor() {
super();
}
}

const cat = new Cat();

console.log(instOf(cat, Cat)); // true
console.log(instOf(cat, null)); // false