前端面试题系列文章:
- 【1】备战前端实习面试之HTML篇
- 【2】备战前端实习面试之CSS篇
- 【3】备战前端实习面试之JavaScript篇
- 【4】备战前端实习面试之React篇
- 【5】备战前端实习面试之Vue篇
- 【6】备战前端实习面试之Node.js篇
- 【7】备战前端实习面试之浏览器篇
- 【8】备战前端实习面试之性能优化篇
- 【9】备战前端实习面试之计算机网络篇
- 【10】备战前端实习面试之手写代码篇
- 【11】备战前端实习面试之代码输出结果篇
React
事件机制
React 并不是将 click
等事件绑定到了 div 的真实 DOM 上,而是在 document
处监听所有事件,当事件发生并且冒泡到 document
处的时候,React 将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂载销毁时统一订阅和移除事件。
冒泡到 document
上的事件也不是原生的浏览器事件,而是由 React 自己实现的合成事件,因此如果不想要是事件冒泡的话应该调用 event.preventDefault()
方法,而不是调用 event.stopProppagation()
方法。
实现合成事件的目的如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果有很多的事件监听,那就需要分配很多的事件对象,造成高额的内存分配问题。但对于合成事件来说,有一个事件池专门来管理他们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
React 的合成事件和普通的 HTML 事件有什么不同
- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
- 对于事件函数处理语法,原生事件为字符串,react 事件为函数;
- react 事件不能采用
return false
的方式来阻止浏览器的默认行为,而必须要明确地调用preventDefault()
来阻止默认行为。
合成事件的优点在于:
- 兼容所有浏览器,更好的跨平台;
- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收);
- 方便 React 统一管理和事务机制。
在 React 底层,主要对合成事件做了两件事:
- 事件委派:React 会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器维持了一个映射来保存所有组件内部事件和处理函数。
- 自动绑定:React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。
对 React Fiber 的理解,它解决了什么问题?
React V15 在渲染时,会递归对比 VirtualDOM 树,找出需要变动的节点,然后同步更新他们,一气呵成。这个过程期间,React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。
为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。可以将浏览器的渲染、布局、绘制、资源加载、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率,同时兼顾任务执行效率。
所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
- 分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
核心思想:Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),他只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
对 componentWillReceiveProps
的理解
该方法当 props
发生变化时执行,初始化 render
时不执行。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState()
来更新你的组件状态,旧的属性还是可以通过 this.props
来获取,这里调用更新状态是安全的,并不会触发额外的 render
调用。
使用好处:在这个生命周期中,可以在子组件的 render
函数执行前获取新的 props
,从而更新子组件自己的 state
。可以将数据请求放在这里执行,需要传的参数则从 componentWillReceiveProps(nextProps)
中获取,而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。
对 React-Intl 的理解,它的工作原理?
React-intl是雅虎的语言国际化开源项目FormatJS的一部分,通过其提供的组件和API可以与ReactJS绑定。
React-intl提供了两种使用方法,一种是引用React组件,另一种当 DOM 操作比较频繁时, React 底层会先将前后两次的虚拟 DOM 树进行对比,定位出具体需要更新的部分,生成一个补丁集
React组件的构造函数有什么作用?它是必须的吗?
构造函数主要用于两个目的:
- 通过将对象分配给
this.state
来初始化本地状态 - 将事件处理程序方法绑定到实例上
类组件与函数组件有什么异同?
相同点:组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素,也正是因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。
不同点:
- 类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是
immutable
、没有副作用、引用透明等特点。 - 性能优化上,类组件主要依靠
shouldComponentUpdate
阻断渲染来提升性能,而函数组件依靠React.memo
缓存渲染结果来提升性能。 - 类组件需要继承
class
,函数组件不需要。 - 类组件可以访问生命周期方法,函数组件不能。
- 类组件可以定义并维护
state
,函数组件不能。 - 类组件中可以获取到实例化后的
this
,并基于这个this
做各种各样的事情,函数组件不可以。
React setState 调用之后发生了什么?是同步还是异步?
除了setState能改变状态值,还有什么方法
React组件的state和props有什么区别?
- props
props
是一个从外部传进组件的参数,从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的 props
来重新渲染子组件,否则子组件的 props
以及展现形式不会改变。
- state
state 的主要作用是用于组件保存、控制以及修改自己的状态,它只能在 constructor
中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的 this.setState
来修改,修改 state
属性会导致组件的重新渲染。
- 区别
props
是传递给组件的,而state
是在组件内被组件自己管理的。props
是不可修改的,而state
可以修改。
React 的生命周期有哪些
React 通常将组件生命周期分为三个阶段:
- 装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
- 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
- 卸载过程(Unmount),组件从DOM树中被移除的过程;
React Hooks 有哪些,详细描述&功能
React Hooks 是 React16.8引入的一种新特性,它们允许你在不编写类组件的情况下使用状态和其他 React 功能。
useState
:useState
是最常用的 React Hook 之一,用于在函数组件中添加状态。它接受一个初始值作为参数,并返回一个包含当前状态和更新状态的函数数组。你可以使用返回的函数来读取和更新状态值。useEffect
:useEffect
允许你在函数组件中执行副作用操作,比如订阅事件、发送网络请求或手动修改 DOM 。它接受一个回调函数和一个依赖数组作为参数。回调函数在 每次渲染时都会被调用 ,但你可以通过依赖数组来控制何时重新调用该回调函数。useContext
:useContext
用于在函数组件中访问 React 的上下文(context)。它接受一个上下文对象(通过React.createContext
创建),并返回该上下文的当前值。使用useContext
,你可以避免在组件层级中显式地传递上下文。useReducer
:useReducer
是另一种用于管理状态的 Hook。它接受一个状态更新函数和一个初始状态作为参数,并返回一个包含当前状态和触发状态更新的dispatch
函数的数组。它通常与useContext
一起使用,以实现复杂的状态管理。useRef
:useRef
返回一个可变的ref
对象,它在组件的整个生命周期中保持不变。它通常用于获取或存储对 DOM 节点的引用,以及在每次渲染之间存储任意值。useMemo
:useMemo
用于在组件渲染过程中缓存计算结果。它接受一个回调函数和依赖数组作为参数,并返回计算结果。在依赖数组不变的情况下,useMemo
会返回上一次计算的结果,从而避免不必要的重复计算。useCallback
:useCallback
类似于 useMemo,但它用于缓存函数。它接受一个回调函数和依赖数组,并返回一个记忆化的回调函数。这在将回调函数作为 props 传递给子组件时特别有用,以避免不必要的重新渲染。
React Hooks 解决了哪些问题?
- 状态逻辑复用:在使用类组件时,复用组件之间的状态逻辑需要使用高阶组件 (Higher-Order Components) 或 Render Props,而 Hooks 提供了一种更简洁、直接的方式来共享和复用状态逻辑,通过自定义的 Hook 可以将状态逻辑封装起来,并在多个组件中重用。
- 状态管理的复杂性:在使用类组件时,复杂的状态管理往往需要使用类似 Redux 这样的状态管理库,而 Hooks 中的
useState
和useReducer
可以更直接地管理组件的状态,减少了引入额外状态管理库的需求。 - 生命周期的混乱:在类组件中,生命周期方法 (Lifecycle methods) 可能会导致代码的分散和逻辑的混乱,而 Hooks 提供了一种更直观的方式来处理组件的生命周期,通过
useEffect
可以集中处理副作用操作,并且可以更细粒度地控制副作用的执行时机。 - 对于函数组件的限制:在过去,函数组件只能是无状态的(Stateless),无法拥有自己的内部状态和生命周期方法,而 Hooks 的引入使函数组件具备了处理状态和副作用的能力,使得函数组件可以代替类组件的使用,从而更好地组织和复用代码。
- 难以理解的闭包和 this:在类组件中,由于 JavaScript 中
this
的工作机制以及闭包的影响,很容易在事件处理函数中出现问题,而 Hooks 的使用可以避免这些问题,使得事件处理函数更简洁、直观。
说说真实 DOM 与虚拟 DOM 的区别,优缺点
真实 DOM
真实 DOM 是浏览器中实际存在的 DOM 树,它是由浏览器解析 HTML 并构建的,它代表着当前页面的结构和内容。当页面发生变化时,真实 DOM 需要进行重新渲染,重新计算布局和绘制,这个过程比较耗费性能,特别是当 DOM 树较为庞大时。
虚拟 DOM
虚拟 DOM 是一个轻量级的 JavaScript 对象树,它是对真实 DOM 的抽象表示。它通过在内存中维护一个与真实 DOM 结构相对应的虚拟 DOM 树来跟踪页面的状态。当页面状态发生变化时,虚拟 DOM 可以高效地计算出与最终渲染结果对应的最小 DOM 更新,并将这些更新批量应用到真实 DOM 上,从而减少了不必要的 DOM 操作和页面重绘,提高了性能。
优缺点
- 虚拟 DOM 相对于真实 DOM 具有以下优点:
- 性能优化:虚拟 DOM 可以通过比较两个虚拟 DOM 树的差异来最小化对真实 DOM 的操作,从而提高页面渲染性能。
- 简化复杂度:使用虚拟 DOM 可以将页面状态的管理和更新逻辑进行抽象和封装,使代码更易于理解、维护和重用。
- 跨平台能力:虚拟 DOM 不仅可以在浏览器环境下使用,还可以在其他平台上使用,如服务器端渲染和原生应用开发。
- 然而,虚拟 DOM 也存在一些缺点:
- 内存消耗:使用虚拟 DOM 需要在内存中维护一份额外的数据结构,这会增加一定的内存消耗。
- 引入复杂性:引入虚拟 DOM 增加了框架本身的复杂性,同时开发者需要学习和掌握虚拟 DOM 的概念和使用方式。
虚拟 DOM 大概是如何工作的
虚拟 DOM 的工作原理可以概括为以下几个步骤:
- 初始化阶段:在初始渲染时,通过解析 HTML 或使用 JSX,创建一个初始的虚拟 DOM 树。这个虚拟 DOM 树是一个纯 JavaScript 对象,与真实 DOM 结构一一对应,但不包含任何与浏览器相关的操作。
- 状态变更:当应用的状态发生变化时,触发重新渲染。这可能是由用户交互、数据更新或其他事件引起的。
- 生成新的虚拟 DOM 树:根据状态变更,生成一个新的虚拟 DOM 树。这个树与之前的虚拟 DOM 树进行比较,找出发生变化的部分。
- 差异计算:通过对比前后两个虚拟 DOM 树的差异,找到需要更新的节点。这个过程通常使用一种叫作 “diffing” 的算法来计算差异。
- 批量更新:将计算得到的需要更新的节点集合应用到真实 DOM 上。这个过程通过一系列 DOM 操作(如添加、删除、修改节点)来进行。由于是批量更新,避免了频繁的 DOM 操作,提高了性能。
- 重新渲染:当差异计算和批量更新完成后,页面上的真实 DOM 结构已经与最新的虚拟 DOM 树保持一致,从而完成了重新渲染。
通过虚拟 DOM,React 在每次状态变更时,不直接操作真实 DOM,而是通过对比虚拟 DOM 树的差异,最小化真实 DOM 的更新操作。这样可以减少浏览器的重绘和回流,提高页面的性能和响应速度。
需要注意的是,虚拟 DOM 并不意味着一定会比手动操作真实 DOM 快,而是通过优化更新过程来提高性能。在一些简单的场景下,直接操作真实 DOM 可能更为高效。虚拟 DOM 的优势主要在于处理复杂页面和频繁状态变更时能够提供更好的性能。
说说 Diff 算法
diff 操作本身是有性能损耗的,React文档中提到,即使在最前沿的算法中,将前后两棵树完全比对的算法的复杂程度为 O(n³),其中n是树中元素的数量。
为了降低算法复杂度,React 的 diff 设置了三个限制条件,以确保复杂度能降为O(n)
:
- 只对同级元素进行 diff 。如果一个
DOM 节点
在前后两次更新中跨越了层级,那么 React 不会复用该节点。 - 只复用同类型的 DOM 节点。如果元素由
div
变为p
,React 会销毁div
及其子孙节点,并新建p
及其子孙节点。 - 通过元素的
key
属性判断是否可以复用。
React 通信方式
Props
(属性):通过组件的 props 属性,可以将数据从父组件传递给子组件。父组件可以通过在子组件的标签中设置属性值,子组件可以通过 this.props 来获取父组件传递的数据。Callback
(回调函数):父组件可以将一个函数作为 prop 传递给子组件,子组件可以在需要的时候调用该函数并传递参数,实现与父组件的通信。Context
(上下文):Context 提供了一种在组件树中共享数据的方式,避免了 props 层层传递的繁琐。通过创建一个 Context 对象,并使用 Provider 和 Consumer 组件,在组件树中的任意位置共享数据。Redux
(状态管理):Redux 是一种可预测的状态管理容器,可以在 React 应用中实现全局状态管理。通过定义全局的状态和操作,组件可以从 Redux Store 中获取状态并触发操作来更新状态。React Router
(路由):React Router 是一个用于处理页面路由的库,可以实现页面之间的导航和传递参数。通过路由配置和 URL 参数,组件可以根据路由信息来展示不同的内容。
受控组件与非受控组件
在 React 中,受控组件和非受控组件是表单元素的两种不同管理方式。
受控组件(Controlled Components)是指由 React 组件来控制其表单元素的值和状态。它们通过在组件的 state 中存储值,并使用事件处理程序来更新 state 和处理表单变化。当用户输入时,组件会根据 state 中的值进行重新渲染,并根据需要更新表单元素的值。受控组件的值和状态完全由 React 组件管理。
非受控组件(Uncontrolled Components)是指表单元素的值和状态由 DOM 自身来管理,而不受 React 组件的控制。在非受控组件中,组件不会维护输入框的值的状态,而是通过引用 DOM 元素来获取最新的值。通常使用 ref 来访问 DOM 元素。
对 React 和 Vue 的理解,它们的异同
相似之处:
- 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库
- 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板。
- 都使用了Virtual DOM(虚拟DOM)提高重绘性能
- 都有props的概念,允许组件间的数据传递
- 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性
不同之处:
- 数据流
Vue默认支持数据双向绑定,而React一直提倡单向数据流
- 虚拟DOM
Vue2.x开始引入”Virtual DOM”,消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。
- Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
- 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。
- 组件化
React与Vue最大的不同是模板的编写。
- Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。
- React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。
具体来讲:React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 完组件之后,还需要在 components 中再声明下。
- 监听数据变化的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
- React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。
- 高阶组件
- react可以通过高阶组件(Higher Order Components– HOC)来扩展,而vue需要通过mixins来扩展。
- 原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。
- 构建工具
两者都有自己的构建工具
- React ==> Create React APP
- Vue ==> vue-cli
- 跨平台
- React ==> React Native
- Vue ==> Weex