前端面试要点(六):前端框架

250 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

前言

在面试和复习过程中总结的一些前端知识点,记录下来,风格较简洁,尽量涵盖内容要点和简单例子。本文是前端面试系列第六篇:前端框架,持续更新...

一、React

1. 合成事件

React 合成事件是指将原生事件合成为一个 React 事件,之所以要封装自己的一套事件机制,目的是为了实现全浏览器的一致性,抹平不同浏览器之间的差异性。

合成事件与原生事件的区别: 原生事件绑定在真实的 DOM 上,合成事件采用事件冒泡的形式冒泡到 document 上面,事件触发时 React 使用统一的分发函数 dispatchEvent 将指定函数执行。

2. Virtual DOM

Virtual DOM 本质上是 JavaScript 对象,就是对 DOM 的描述。

为什么需要 Virtual DOM?

  1. 减少操作 DOM,将差异一次性更新到 DOM 中;
  2. 避免手动操作 DOM 带来的不一致性。

Virtual DOM 的 diff,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3),但是通常情况下的 DOM 变更是同级的,因此在现代的各种 Virtual DOM 库都是只比较同级差异,在这种情况下我们的时间复杂度是O(n)。

判断列表差异 算法:

  1. 遍历旧的节点列表,查看每个节点是否还存在于新的节点列表中
  2. 遍历新的节点列表,判断是否有新的节点
  3. 在第二步中同时判断节点是否有移动

3. setState 过程是同步还是异步,为什么有时两次 setState 只执行一次

  • setState 是异步的,因为 React 的渲染机制中使用 queueMicrotask 或者 MessageChannel 将更新任务添加进微任务队列或者宏任务队列中以此实现异步渲染。
  • 为什么要使用异步渲染?因为在 React 中会将连续调用的 setState 进行批量更新,这样做的目的,是为了避免短时间内连续调用造成不必要的渲染,增加性能的开销。
  • 连续调用的 setState 的优先级是一样的,在第一个 setState 调用后,再调用第二个时,会将第一个更新任务的优先级与第二个更新任务的优先级进行比较,如果优先级一样,则不会执行第二个更新任务,而是将第二个任务的更新内容与第一个的更新内容进行合并,最终只会进行一次更新渲染,这样的做法叫做批量更新

4. react 为什么需要 fiber,有哪些优点

  • React 递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们,一气呵成,这个过程 React 称为 Reconcilation(协调)。在 Reconcilation 期间,React 会霸占着浏览器资源,一则会导致用户触发的事件得不到响应,二则会导致掉帧,用户可以感知到这些卡顿。
  • React 通过 Fiber 架构,让自己的 Reconcilation 过程变成可被中断,适时地让出 CPU 执行权。
  • 优点:可以让浏览器及时地响应用户的交互;与其一次性操作大量 DOM 节点相比,分批延时对 DOM 进行操作,可以得到更好的用户体验;给浏览器一点喘息的机会,他会对代码进行编译优化及进行热代码优化,或者对 reflow 进行修正。

5. 什么是高阶组件,请举例说明

  • React 高阶组件(HOC)本身不是组件,它是一个参数为组件,返回值也是一个组件的函数
  • 高阶组件作用于强化组件,复用逻辑,提升渲染性能等作用。
  • 例如 react-redux 中的 connect 就是一个高阶组件,传入你写的组件,返回一个新组件,包含了 redux 中的 store 信息。

6. React 如何处理异常

  • React 16 之前的版本并未提供友好的异常捕获和处理方式,一旦发生异常,应用将不能很好的运行。
  • React 通过创建一个异常边界组件捕获其子组件树的异常。异常边界组件在其生命周期方法 componentDidCatch 捕获异常,React 会判断组件是否添加 componentDidCatch 生命周期方法,如果添加了,则调用包含异常处理的更新渲染组件方法。
  • 异常边界组件无法捕获的异常
    1. 异步任务异常
    2. 异常边界组件自身内部的异常

7. React 有哪些性能优化的点

  • React.memo 来缓存组件,这样只有当传入组件的状态值发生变化时才会重新渲染。如果传入相同的值,则返回缓存的组件。
  • 使用 useMemo 缓存大量的计算。
  • 尽量用更改 css 的方式来控制组件的加载和卸载,如更改 opacity
  • 使用 React.Fragment 避免添加额外的 DOM

二、Redux

1. Redux 三大原则

  1. 单一数据源:我们把 Store 看成一个全局对象,而这个对象是唯一的,所有的状态都在 Store 这个状态树中统一配置
  2. State是只读的:想要改变 State 必须通过 Action,而具体使 Action 在 State上 更新生效的是 reducer
  3. Reducer 必须是一个纯函数:Reducer 内部的执行操作必须是无副作用的,不能对 State 进行直接修改,当状态发生变化时,需要返回一个全新的对象代表新的 State

2. Redux 核心逻辑:中间件机制

  • MiddleWare 就是对 dispatch 方法的一个改造,一个变异。
  • 中间件机制的关键是 compose 方法,这里其实也是函数式编程的一个组合的理念,把函数从右至左进行组合,可以理解为上个函数的返回就是下个函数的参数。

三、Vue

1. 数据绑定原理

vue 双向数据绑定采用数据劫持结合发布者-订阅者模式,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。

  • 数据 -> DOM:数据变化时通知 Watcher,然后 Watcher 告诉虚拟 DOM 哪个变量改变了,然后经过 diff 算法更新 DOM。
  • DOM -> 数据:很简单,通过 DOM 的事件回调触发 setter 更新数据。

2. computed 和 watch 的区别

  • computed 相当于 react 中的 useMemo,如果一个数据依赖于其他数据,那么把这个数据设计为 computed 的;
  • watch 相当于 react 中的 useEffect,如果你需要在某个数据变化时做一些事情,使用 watch 来观察这个数据变化。

3. Slot

Slot 通俗的理解就是“占坑”,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中 slot 的位置),相当于 React 中的 children。

4. nextTick 实现原理

  • nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。
  • 会默认使用 microtasks,但在特殊情况下会使用 macrotasks。对于实现 macrotasks ,会先判断是否能使用 setImmediate ,不能的话降级为 MessageChannel,以上都不行的话就使用 setTimeout。

5. 谈谈 keep-alive

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,这样可以避免多次重复渲染降低性能。

keep-alive 的生命周期:

  • 初次进入时:created > mounted > activated,退出后触发 deactivated
  • 再次进入:只会触发 activated

只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中