疫情在家面试总结

164 阅读11分钟

疫情在家的一个月,边工作边面试大大小小面试了4,5次,好多次都是因为hr一听我在上海直接断了我的面试之路,其中我将面试中反复问的问题,整理出来,总结的过程中熟悉一些知识点。也希望可以早日拿到新offer

最后想说一句,今年的行情很差,大家且行且珍惜!

说一说你对事件循环的理解?

首先JS是单线程,代码执行的时候。将不同的函数执行上下文压入栈中进行有序的执行。

在执行同步代码的时候,如果遇到异步的事件,JS引擎并不会一直等待其结果返回,而是将它挂起,继续执行栈中的其他任务,当同步任务执行完了,再将异步事件对应的回调加入到当前执行栈中不同的另一任务列队中等待执行。

任务列队又分为宏任务列队微任务列队,当前执行栈中执行完,JS引擎会首先判断微任务列队中是否有任务可以执行,有的话就放入到栈中执行,当微任务列队中任务执行完了再去判断宏任务列队。上述过程不断的重复就是事件循环

微任务(是由js V8引擎发起的)

Promise.then
process.nextTick
observer

宏任务(由宿主环境node,浏览器)发起的

scrpit
setTimeout
setInterval
I/O

React 有什么特性?

  • JSX 语法
  • 单项数据流
  • 虚拟DOM
  • 声明式函数组件
  • Component

什么是JSX?

JSX是JS的一种语法扩展,他跟模版语言很像,但他充分具备JS的能力。它并不会被浏览器直接支持,需要babel进行转译。

JSX会被babel转译成 React.createElement(),React.createElement()将返回一个叫做ReactElement的js对象。

React 使用JSX,是为了我们前端可以使用最熟悉的HTML标签来创建虚拟DOM在降低学习成本的同时提高研发效率。

说说你对虚拟DOM的理解?

虚拟DOM,是DOM节点在js中的一种抽象的数据结构,本质上是js 对象形式存在的。JSX通过babel的方式转化成React.createElement执行,返回值是一个对象,也就是虚拟DOM。它包括type,props,children三个属性。虚拟DOM的作用是在每一次响应式数据发生改变引起的页面重新渲染,对比前后的DOM,匹配尽可能少的需要更新DOM,从而达到性能提升的目的。

虚拟DOM的总耗损是“虚拟DOM增删改+真实DOM差异增删改+重排与重绘“,真实DOM的总耗损是”真实DOM完全增删改+重排与重绘

比如:

传统的原生api去操作DOM,当你在一次操作中需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后面还有9个更新操作,因此会马上执行流程,最终执行10次流程

而Vnode,同样更新10个节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这js对象一次性的attach到DOM树上,避免了大量无谓的计算。

使用虚拟DOM的优势

  • 简单方便
  • 跨平台:React借助虚拟DOM,带来了跨平台能力,一套代码多端运行。

函数组件和类组件的区别?

类组件,就是通过ES6类的方式去编写的组件,该类必须继承React.Component,在组件中必须使用render方法,在return 中返回一个React对象。 函数组件,就是通过函数的形式去实现一个react组件。函数的第一个参数为props用于接收父组件传递过来的参数。

区别:

  • 编写形式

  • 状态管理

    在hook出来之前,函数就是无状态组件,不能保管组件的状态,不像类组件中调用setState。如果想要管理state状态,可以使用useState

  • 生命周期

    函数组件中,不存在生命周期,这是因为生命周期的钩子都是继承React.Component。 函数组件使用useEffect 也能完全替代生命周期的作用,对应componentDidMount,如果在useEffect回调函数中return一个函数,则return 函数会在组件卸载的时候执行。正如componentWillUnMount

  • 调用方式 函数组件,调用则是执行函数即可。

谈谈Promise和async/await 的区别

Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大,简单来说Promise好比容器,里面存放着一些未来才会执行完毕的事件的结果,而这些结果一旦生成是无法改变的。

async await 也是一种异步编程的一种解决方案,它遵循Generator函数的语法糖,他拥有内置执行器,不需要额外的调用直接会执行并输出结果,他返回的是一个Promise

区别:

  • Promise的出现解决了传统callback函数导致的“地狱回调”问题,但它的语法导致了它纵向发展形成了一个回调链,遇到复杂的业务场景,这样的语法显然不美观,而async await代码看起来会简洁些,使得异步的代码看起来像同步代码。await的本质是可以提供等同于“同步效果”的等待异步返回能力的语法糖,只有这一行代码执行完,才会执行下一句。

  • async await和promise一样,也是非俎塞的

  • async await 是基于promise实现的,可以是改良版的Promise,它不能用于普通的回调函数。

两者除了语法糖不一样,他们解决的问题,达到的效果大同小异的。

position有几个值,分别是什么?

  • relative:相对定位; 不会使元素脱离文档流(原本的位置会被保留,即改变位置也不会占用新位置),没有定位偏移量时对元素无影响(相对于自身原本位置进行偏移),不会影响元素本身的特性(无论区块元素还是内联元素会保留其原本的特性)
  • absolute:绝对定位; 元素完全脱离文档流,使得区块元素在未设置宽高的时内容撑开宽度,相对于最近一个有定位的父元素偏移(若夫元素没有定位则逐层上找,直到document)
  • fixed:固定定位; fixed生成的固定定位的元素,相对于浏览器窗口进行定位
  • static:默认值; 默认布局,元素出现在正常的文档流
  • sticky:粘性定位; 基于用户滚动的位置,它的行为就像position:relative,而当页面滚动超出目前区域时,他的表现就像fixed,会固定在目标位置
  • inherit; 规定应该从父元素继承position属性的值

说说你对React Hooks的理解?它解决了什么问题?

Hook是React16.8新增的特性,它可以让你在不编写class的情况下使用state以及其他的React特性。为什么引入Hook,官方给出的解释是解决长时间使用和维护React过程中常遇到的问题。

比如:

  1. 难以重用和共享组件中与状态相关的逻辑
  2. 逻辑复杂的组件难以开发与维护
  3. 类组件中的this增加学习成本
  4. 由于业务变动,函数组件不得不改为类组件等等

常见的Hooks

useState,useEffect,useContext,useReducer,useCallback,useMemo,useRef

解决的问题:

hooks能够更容易解决状态相关的重用问题 hooks的出现,使函数组件的功能得到了扩充,拥有了类组件的相似功能,使用hooks能够解决大多数问题,并且还拥有代码复用的机制。

说说React Router有几种模式,实现的原理是什么?

在单页应用中,一个web项目只有一个Html页面,一旦页面加载完成后,就不用因为用户的操作而进行页面的重新加载或者跳转,其特性如下:

1、改变URL且不让浏览器向服务器发送请求
2、在不刷新页面的前提下动态改变浏览器地址栏中的URL地址

其中主要分成了两种模式

  • hash 模式 在url后面加上# - history 模式 允许操作浏览器曾经在标签栏或者框架中访问的会话历史记录

React Router 对应的hash模式和history模式对应的组件分别为HashRouterBrowserRouter,这两个组件使用都是作为最顶层组件包裹其他组件

hashRouter的原理:

改变hash值并不直接导致浏览器向服务器发送请求,浏览器不发送请求,也就不会刷新页面。hash值的改变,会触发全局的window对象上的hashChange事件,所以hash模式路由就是利用hashChange事件监听URL的变化,从而进行DOM操作来模拟页面跳转的。

React-router也是基于这个特性实现的路由跳转。

1、HashRouter

HashRouter包裹了整个应用,通过Window.addEventListener("hashChange",callback)监听hash值的变化,并传递给其嵌套的组件,然后通过context将location数据往后代组件传递。

2、Router

Router组件主要做的就是通过BrowserRouter传过来的当前值,通过props传进来的path与context传进来的pathname进行匹配,然后决定是否执行渲染组件。

说说你是如何提高组件的渲染效率的?在React总如何避免不必要的render?

React是基于虚拟DOM和高效的Diff算法的完美配合,实现了对DOM最小粒度的更新,大多数情况下,React对DOM的渲染效率足以满足我们的业务日常。

我们知道Render的触发时机,简单来讲就是类组件通过调用setState方法,就会导致Render,父组件一旦发生了render渲染,自组件也一定会执行render渲染。

父组件导致自组件渲染,子组件并没有发生任何的改变,这时候就可以避免无谓的渲染。可通过:

 shouldComponentUpdate
 PureComponent
 React.memo
 

ShouldComponentUpdate 对比state和props,确定是否要重新渲染,默认情况返回true,如不希望渲染,返回false即可。

PureComponent 通过对比state和props的浅比较结果来实现shouldComponentUpdate,对应的源码

if(this._compositeType === Composite.PrueClass){
    shuldUpdate = !shallowEqual(prevProps,nextProps)|| !shallowEqual(inst.state,nextState)
}

shallowEqual对应的方法大致如下

const hasOwnProperty = Object.prototype.hasOwnProperty;
// is方法判断两个值是否相等
function is(x,y){
    if(x===y){
        return x!==0 || y!==0 || 1/x === 1/y;
    }else{
        return x!==x && y!==y
    }
}
function shallowEqual(objA,objB){
// 先对基本类型进行判断
    if(is(objA,objB){
        return true
    }
    if(typeof objA !== "object" || objA===null || typeof objB!=="object" || objB===null){
        return false
    }
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// 长度不相等,直接返回false
if(keysA.length !== keysB.length){
    return false
}
// key相等的情况下,再去循环比较
for(let i=0;i<keysA.length;i++){
    if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
          return false;
        }
 }
return true

React.memo 用来缓存组件的渲染,避免不必要的更新,与pureComponent十分类似,但不同的是react.memo只能用在函数组件,如果需要深层次比较,这时候可以给memo第二个参数比较函数

function arePropsEqual(prevProps,nextProps){
    retutn prveProps === nextProps
}
export default mome(Buttom,arePropsEqual)

说说React性能优化的手段有哪些?

1、避免使用内联函数
    使用内链函数,每次调用render函数的时候都会创建一个新的函数。
2、使用react.Fragments 避免额外标记
3、懒加载组件
    在react中使用suspense和lazy组件实现代码的拆分功能。
4、事件绑定方式
    render方法中使用bind和箭头函数这两种形式在每次组件render的时候都会生成新的实例。
    而constructor中bind事件与定义阶段使用箭头函数绑定这两种形式只会生成一个方法实例,性能方面会有所提升。
5、服务端渲染
    服务端渲染,需要起一个node服务,可以使用express,koa等,调用react的renderToString方法,将跟组件渲染成字符串,在输出到响应中。
6、使用immutable

说说你在react项目中是如何捕获错误的?

异常错误:举个例子,在react项目中去编写组件内部的js代码错误会导致react的内部状态被破坏,导致整个应用崩溃,这是不应该出现的现象。

作为一个框架,react也有自身对于错误的处理解决方法。

为了解决出现的错误导致整个应用崩溃的问题,react16引用了错误边界的概念。错误边界是一种react组件,这种组件可以捕获发生在其子组件树任何位置的js错误,并打印这些错误,同时展示降级UI,而并不会渲染那些发生崩溃的子组件树。

错误边界在渲染期间,生命周期方法和整个组件树的构造函数中捕获错误

抛出错误后,请使用static getDerivedStateFormError()渲染备用UI,使用componentDidCatch()打印错误信息。

class ErrorBoundary extends React.Component{
    constructor(props){
        super(props);
        this.state = {hasError:false}
    }
    static getDerivedStateFormError(error){
        return {hasError:true}
    }
    componentDidCatch(error,errorInfo){
        // 错误日志处理
    }
    render(){
        if(this.state.hasError){
        // 自定义降级ui并渲染
            return <h1>something went wrong</h1>
        }
        return this.props.children
    }
}

然后就可以把自身组件作为错误边界的子组件,

<ErrorBoundary><UI></ErrorBoundary>

下面这些情况无法捕获到异常

  • 事件处理
  • 异步代码
  • 服务器渲染
  • 自身抛出来的错误

对于错误边界无法捕获的异常,如时间允许处理过程中发现的问题,可以使用try...catch...

除此之外,还可以监听onerror事件 window.addEventListener("error",function(event){...})

说说react服务端渲染怎么做的?原理是什么?

SSR-意为服务器渲染,指的是由服务侧完成页面HTML结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。

在react中,实现SSR主要有两种形式

手动搭建一个SSR框架
使用成熟的SSR框架,如Next.JS

node server 接收到客户端请求,得到当前的请求url路径,然后再已有的路由表中查找对应的组件,拿到需要请求的数据,将数据作为Props,context,或者store形式传入组件。

然后基于react内置的服务端渲染方法renderToString()把组件渲染为html字符串在最终的html进行输出前需要将数据注入到浏览器端。

浏览器进行渲染和节点对比,然后执行完成组件内时间绑定和一些交互,浏览器重用了服务器输出的html节点,整个路程结束