面试题整理

说说React生命周期中有哪些坑?如何避免

getDerivedStateFromProps 容易编写反模式代码,使受控组件与非受控组件区分模糊。

componentWillMount 在 React 中已被标记弃用,不推荐使用,主要原因是新的异步渲染架构会导致它被多次调用。所以网络请求及事件绑定代码应移至 componentDidMount 中。

componentWillReceiveProps 同样被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。

shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。

componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。

如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。

如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加。

避免方法:

不在恰当的时候调用了不该调用的代码;

在需要调用时,不要忘了调用。

说说Real diff算法是怎么运作的?

主要分为3层:tree层、component层、element层

tree层:

tree层对DOM节点的跨层级移动的操作忽略不计,只对相同层级的DOM节点进行比较(即同一个

父节点下的所有子节点),一旦发现节点不存在,直接删除掉该节点以及之下的所有子节点。

component层:

component层是组件间的比较,有三种策略:

遇到同一类型的组件遵循 tree diff,进行层级对比

遇到不同类型的组件,直接将这个不同的组件判断为脏组件,并替换该组件和之下所有的子节点

在同一类型的两个组件中,当知道这个组件的虚拟dom没有任何变化,就可以手动使用shouldComponentUpdate()来判断组件是否需要进行diff,进一步的提升了diff效率和性能

注意:

避免使用结构相同但是类型不同的组件,由于类型不同的原因,diff会直接销毁该组件并重建,会

造成的性能浪费;

对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化

element层:

element层对同一层级的元素节点进行比较,有三种情况:

面对全新的节点时,执行插入操作

面对多余的节点时,执行删除操作

面对换位的节点时,执行移动操作

react中key值的作用

key值就是每个元素节点对应的唯一标识,要进行数据的更新,需要进行新旧虚拟dom的对比,就需要对比子元素的key值是否有匹配项,如果有则会进行数据的更新;如果没有就需要进行删除和重新创建

调和阶段setState干了什么

  • 代码调用setState函数之后,React会将传入的参数对象与组件当前状态合并,然后触发所谓的调和过程(Reconciliation)

  • 经过调和过程,React会以相对高效的方式根据新的状态构建React元素树并且着手重新渲染整个UI界面

  • 在React得到元素树之后,React会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染

  • 在差异计算算法中,React能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染

setState是同步还是异步

合成事件中是异步,钩子函数中式异步,原生事件中是同步,setTimeout中是同步

说说redux的实现原理是什么

  • 将应用的状态统一放到state中,由store来管理state。

  • reducer的作用是返回一个新的state去更新store中对用的state。

  • 按redux的原则,UI层每一次状态的改变都应通过action去触发,action传入对应的reducer 中,reducer返回一个新的state更新store中存放的state,这样就完成了一次状态的更新.

  • subscribe是为store订阅监听函数,这些订阅后的监听函数是在每一次dipatch发起后依次执行

  • 可以添加中间件对提交的dispatch进行重写

React合成事件的原理

React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。

合成事件总结:

  • 合成事件的监听器是统一注册在document上的,且仅有冒泡阶段。所以原生事件的监听器响应总是比合成事件的监听器早

  • 阻止原生事件的冒泡后,会阻止合成事件的监听器执行

React组件之间如何通信

  • 父组件向子组件传递

  • 子组件向父组件传递

  • 兄弟组件之间的通信

  • 父组件向后代组件传递

  • 非关系组件传递

父组件向子组件传递:

父组件在调用子组件的时候,只需要在子组件标签内传递参数,子组件通过props属性就能接收到父组件传递过来的参数

子组件向父组件传递:

父组件向子组件传递一个函数,然后通过这个函数的回调拿到子组件传过来的值

父组件向后代组件传递:

父组件向后代组件传递数据是一件最普通的事情,就像全局数据一样

使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据

通过使用React.createContext创建一个context

const PriceContext = React.createContext('price')

context创建成功后,其下存在Provider组件用于创建数据源,Consumer组件用于接收数据,使用实例如下

Provider组件通过value属性用于给后代组件传递数据:

<PriceContext.Provider value={100}>
</PriceContext.Provider>

如果想要获取Provider传递的数据,可以通过Consumer组件或者或者使用contextType属性接收,对应分别如下:

class MyClass extends React.Component {
  static contextType = PriceContext;
  render() {
    let price = this.context;
    /* 基于这个值进行渲染工作 */
  }
}

Consumer组件:

<PriceContext.Consumer>
    { /*这里是一个函数*/ }
    {
        price => <div>price:{price}</div>
    }
</PriceContext.Consumer>

非关系组件传递

如果组件之间关系类型比较复杂的情况,建议将数据进行一个全局资源管理,从而实现通信,例如redux

为什么react元素有一个$$type属性

React 0.13很容易受到 XSS 攻击。再次声明,这个攻击是服务端存在漏洞导致的

React 0.14 修复手段是用 Symbol 标记每个 React 元素

JSON不支持 Symbol 类型。所以即使服务器存在用JSON作为文本返回安全漏洞,JSON 里也不包含 Symbol.for('react.element')。React 会检测 element.$$typeof,如果元素丢失或者无效,会拒绝处理该元素。

说说Connect组件的原理是什么

react-redux用于连接react组件及redux,方便开发者使用redux,管理状态。其中connect方法是关键

connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件

说说你对fiber架构的理解?解决了什么问题

问题

JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待

如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿

而这也正是 React 15 的 Stack Reconciler所面临的问题,当 React在渲染组件时,从开始到渲染完成整个过程是一气呵成的,无法中断

如果组件较大,那么js线程会一直执行,然后等到整棵VDOM树计算完成后,才会交给渲染的线程

这就会导致一些用户交互、动画等任务无法立即得到处理,导致卡顿的情况

是什么

React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认,React Fiber 在React 16 版本发布

在react中,主要做了以下的操作:

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务

  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行

  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行

  • 从架构角度来看,Fiber 是对 React核心算法(即调和过程)的重写

Fiber把渲染更新过程拆分成多个子任务,每次只做一小部分,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行

即可以中断与恢复,恢复后也可以复用之前的中间状态,并给不同的任务赋予不同的优先级,其中每个任务更新单元为 React Element 对应的 Fiber节点

说说你对redux中间件的理解?常用的中间件有哪些?实现原理?

中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的

Redux的整个工作流程,action发出之后,reducer立即算出state,整个过程是一个同步的操作

那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件

常用中间件

  • redux-thunk:用于异步操作

  • redux-promise:用于异步操作

  • redux-logger:用于日志记录

原理

所有中间件被放进了一个数组,然后嵌套执行,最后执行store.dispatch

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

  • 避免使用内联函数:如果我们使用内联函数,则每次调用render函数时都会创建一个新的函数实例,我们应该在组件内部创建一个函数,并将事件绑定到该函数本身。这样每次调用 render 时就不会创建单独的函数实例

  • 使用 React Fragments 避免额外标记:新组件具有单个父标签父级不能有两个标签,所以顶部要有一个公共标签,所以我们经常在组件顶部添加额外标签div,额外标签除了充当父标签之外,并没有其他作用,这时候则可以使用fragement(<></>)其不会向组件引入任何额外标记,但它可以作为父级标签的作用

  • 事件绑定方式:从性能方面考虑,在render方法中使用bind和render方法中使用箭头函数这两种形式在每次组件render的时候都会生成新的方法实例,性能欠缺,而constructor中bind事件与定义阶段使用箭头函数绑定这两种形式只会生成一个方法实例,性能方面会有所改善

  • 使用 Immutable:Immutable可以给 React 应用带来性能的优化,主要体现在减少渲染的次数,Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象

  • 懒加载组件:在react中使用lazy组件

  • 服务端渲染:服务端渲染,需要起一个node服务,可以使用express、koa等,调用react的renderToString方法,将根组件渲染成字符串,再输出到响应中

  • 组件卸载前进行清理操作:在组件中为 window 注册的全局事件, 以及定时器, 在组件卸载前要清理掉, 防止组件卸载后继续执行影响应用性能

说说你对事件循环event loop的理解?

JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

在JavaScript中,所有的任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行

  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

异步任务还可以细分为微任务与宏任务

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中

  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

微任务:

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前,常见的微任务有:

  • Promise.then

  • MutaionObserver

  • Object.observe(已废弃;Proxy 对象替代)

  • process.nextTick(Node.js)

宏任务:

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

  • script (可以理解为外层同步代码)

  • setTimeout/setInterval

  • UI rendering/UI事件

  • postMessage、MessageChannel

  • setImmediate、I/O(Node.js)

前端跨域的解决方案?

跨域是 浏览器 为了安全而作出的限制策略(所以服务端不涉及到跨域)

浏览器请求必须遵循同源策略,即同域名、同端口、同协议;

后端设置cors跨域

  • 服务端进行接口请求设置,前端直接调用

  • 说明:后台设置前端某个站点进行访问

JSONP (动态创建script标签)

  • JSONP跨域-前端适配,后端配合

  • 前后端同时改造

接口代理:

  • 通过修改nginx服务器配置实现代理转发

  • 前端修改,后端不用

数组常用方法及作用,至少15个

增:

  • push():方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度

  • unshift():在数组开头添加任意多个值,然后返回新的数组长度

  • splice():传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组

  • concat():首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组

删:

  • pop():删除数组的最后一项,同时减少数组的length 值,返回被删除的项

  • shift():删除数组的第一项,同时减少数组的length 值,返回被删除的项

  • splice():传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组

  • slice():创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

查:

  • indexOf():返回要查找的元素在数组中的位置,如果没找到则返回 -1

  • includes():返回要查找的元素在数组中的位置,找到返回true,否则false

  • find():返回第一个匹配的元素

排序方法

  • reverse():将数组元素方向反转

  • sort():接受一个比较函数,用于判断哪个值应该排在前面

转换方法:

join():接收一个参数,即字符串分隔符,返回包含所有项的字符串

迭代方法:

  • some():对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true

  • every():对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true

  • forEach():对数组每一项都运行传入的函数,没有返回值

  • filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回

  • map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

React render方法的原理,在什么时候会触发?

render函数里面可以编写JSX,转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM

在React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行,函数组件使用useState更改状态不一定导致重新render

组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state

在这种情况下,父组件或者祖先组件的 state 发生了改变,就会导致子组件的重新渲染

所以,一旦执行了setState就会执行render方法,useState 会判断当前值有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染

说说你对vue中mixin的理解?

mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。

本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等

我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来

定义一个modal弹窗组件,内部通过isShowing来控制显示

const Modal = {
  template: '#modal',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}

定义一个tooltip提示框,内部通过isShowing来控制显示

const Tooltip = {
  template: '#tooltip',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}

通过观察上面两个组件,发现两者的逻辑是相同,代码控制显示也是相同的,这时候mixin就派上用场了

首先抽出共同代码,编写一个mixin

const toggle = {
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}

两个组件在使用上,只需要引入mixin

const Modal = {
  template: '#modal',
  mixins: [toggle]
};
 
const Tooltip = {
  template: '#tooltip',
  mixins: [toggle]
}

for...in循环和for...of循环的区别

for in会遍历数组所有的可枚举数据类型,for of适用于遍历可迭代对象

for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值

for in总是得到对象的key或数组、字符串的下标

for of总是得到对象的value或数组、字符串的值

Js数据类型判断都有哪几种方式?至少说出5种?它们的区别是什么?

typeof:可以判断数据类型,它返回表示数据类型的字符串(返回结果只能包括number,boolean,string,function,object,undefined)

instanceof:A instanceof B,判断A是否是B的实例,如果 A 是 B 的实例,则返回 true,否则返回 false,由构造类型判断出数据类型

constructor:constructor是prototype对象上的属性,指向构造函数,除了undefined和null之外,其他类型都可以通过constructor属性来判断类型

Object.prototype.toString :使用Object.prototype.toString.call()的方式来判断一个变量的类型是最准确的方法,返回格式为[object xxx]

jquery.type():如果对象是undefined或null,则返回相应的“undefined”或“null”。

说说你对webSocket的理解

WebSocket,是一种网络传输协议,位于OSI模型的应用层。可在单个TCP连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅

客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输

特点

  • 全双工

  • 通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合

  • 二进制帧

  • 采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比http/2,WebSocket更侧重于“实时通信”,而HTTP/2 更侧重于提高传输效率,所以两者的帧结构也有很大的区别

  • 不像 HTTP/2 那样定义流,也就不存在多路复用、优先级等特性

  • 自身就是全双工,也不需要服务器推送

  • 协议名

  • 引入ws和wss分别代表明文和密文的websocket协议,且默认端口使用80或443,几乎与http一致

  • 握手

  • WebSocket也要有一个握手过程,然后才能正式收发数据

应用场景:

  • 弹幕

  • 媒体聊天

  • 协同编辑

  • 基于位置的应用

  • 体育实况更新

  • 股票基金报价实时更新

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值