目录
- 1.说说你对react的理解?有哪些特性?
- 2.说说Real diff算法是怎么运作的?
- 3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
- 4. 说说你对React中虚拟dom的理解?
- 5.说说你对react hook的理解?
- 6.React组件之间如何通信?
- 7.说说你对受控组件和非受控组件的理解?应用场景?
- 8.说说Connect组件的原理是什么?
- 9.说说react 中jsx语法糖的本质?
- 10.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
- 11.说说AMD、CMD、commonJS模块化规范的区别?
- 12.说说package.json中版本号的规则?
- 13.说说React jsx转换成真实DOM的过程?
- 14.说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
- 15.React render方法的原理,在什么时候会触发?
- 16.React性能优化的手段有哪些?
- 17.如何通过原生js实现一个节流函数和防抖函数?
- 18.说说你对koa中洋葱模型的理解?
- 19.说说如何借助webpack来优化前端性能?
- 20.说说你对webSocket的理解?
1.说说你对react的理解?有哪些特性?
React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟 DOM 来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流
帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
react 类组件使用一个名为 render() 的方法或者函数组件return,接收输入的数据并返回需要展示的内容
React 特性有很多,如:JSX 语法、单向数据绑定、虚拟 DOM、声明式编程、Component
声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做。它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件
Component在 React 中,一切皆为组件。通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件
组件可以是一个函数或者是一个类,接受数据输入,处理它并返回在 UI 中呈现的 React 元素
2.说说Real diff算法是怎么运作的?
第一次render在执行的时候会将第一次的虚拟dom做一次缓存,第二次渲染的时候会将新的虚拟dom和老的虚拟dom进行对比。这个对比的过程其实就是diff算法。
在DOM需要更新的时候,通过diff算法可以 计算出 虚拟DOM 中真正变化的部分,从而只针对变化的部分进行更新渲染,避免”牵一发而动全身“,造成性能浪费。
为了优化diff算法,react中对普通的diff算法实行了三大策略
tree diff
对比时,遇到同一类型的组件遵循 tree diff,进行层级对比
对比时,一旦遇到不同类型的组件,直接将这个不同的组件判断为 dirty component(脏组件),并替换该组件和之下所有的子节点。
对比时,在同一类型的两个组件中,如果你知道这个组件的 Virtual DOM没有任何变化,你(开发者)就可以手动使用 shouldComponentUpdate() 来判断组件是否需要进行diff,进一步的提升了diff效率和性能
优化点:
避免使用结构相同但是类型不同的组件,因为虽然组件的结构不需要改动,但是由于类型不同的原因,diff会直接销毁该组件并重建,虽然这种情况极少出现,但是造成的性能浪费挺严重的。
对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化
component diff
component diff是组件间的对比
在遇到组件之间的比较时,有三种策略
对比时,遇到同一类型的组件遵循 tree diff,进行层级对比
对比时,一旦遇到不同类型的组件,直接将这个不同的组件判断为 dirty
component(脏组件),并替换该组件和之下所有的子节点。
对比时,在同一类型的两个组件中,如果你知道这个组件的 Virtual
DOM没有任何变化,你(开发者)就可以手动使用 shouldComponentUpdate()
来判断组件是否需要进行diff,进一步的提升了diff效率和性能
优化点:
避免使用结构相同但是类型不同的组件,因为虽然组件的结构不需要改动,但是由于类型不同的原因,diff会直接销毁该组件并重建,虽然这种情况极少出现,但是造成的性能浪费挺严重的。
对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化
element diff
element diff 是针对同一层级的element节点的
在双方同一层级的节点对比时,有三种情况
面对全新的节点时,执行插入操作 —— INSERT_MARKUP
这点不需要过多解释,面对多余的节点时,执行删除操作 —— REMOVE_NODE
删除操作有两种情况:
组件新集合中有组件旧集合中的类型,但对应的element不可更新,只能执行删除
旧组件不在新集合里面,执行删除,面对换位的节点时,执行移动操作 —— MOVE_EXISTING
比如该层级的组件原本是 [A,B,C,D] ,新的结构为 [A,D,B,C] ,只进行了移动操作。在传统的diff算法中,只要遇见不同(B/D)就删除并重新插入,这样的做法过于粗暴,浪费了很多可以复用的节点,所以在element diff中,对新旧该层级的对比双方都添加了唯一的key值进行区分,只要对应的key值对应的元素没有改变,则只需要执行移动即可。
细节:
新旧节点会遍历后对比下标,新的下标称为lastIndex,旧的称为index,如果lastIndex大于index,需要将节点旧的节点移动到新的位置,相反则不动。
如果没有找到对应位置节点,则执行新增; 如果旧的节点在新的节点组用不到,则执行删除;一般是在最后做删除操作。
特殊情形,最后一个节点移动到第一个位置,会导致,前面的n-1个节点都进行后移,影响性能。尽量避免这样的操作。
3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
挂载阶段:
constructor():在react组件挂载之前,会调用它的构造函数
ComponentWillMount():在render()方法之前调用,并且在初始化挂载及后续更新时都会被调用
ComponentDidMount():在挂载之后(插入dom树中)立即调用
更新阶段:
componentWillReceiveProps():在接收父组件改变后的props需要重新渲染组件时使用的比较多,外部组件频繁的时候会导致效率会比较低
ShouldComponentUpdate():用于控制组件重新渲染的流程,在这return false 可以阻止组件的更新
Render():render()方法是class组件当中唯一必须实现的方法
ComponentWillUpdate():ShouldComponentUpdate()返回true后,组件进入重新渲染之前进入这个函数
ComponentDidUpdate():每次改变state重新渲染页面后都会进入到这个生命周期
卸载销毁阶段
ComponentWillUnmount():在此完成组件的卸载和数据的销毁
4. 说说你对React中虚拟dom的理解?
虚拟dom不会进行排版和重绘的操作,我们使用虚拟dom来减少对真实dom的操作,可以达到使用虚拟dom提高性能,但是在首屏需要加载大量的dom时使用虚拟dom就会比不使用虚拟dom的速度要慢,因为虚拟dom会对真实dom进行一次对比
5.说说你对react hook的理解?
可以使用hooks从组件中提取状态逻辑,让这些逻辑可以单独测试并且hooks使我们在无需修改组件结构的情况下复用状态逻辑,hooks将组件中互相关联的部分拆解成更小的函数,还可以使用reducer来管理组件内部状态让其更可预测,hooks让我们在非class组件的情况下使用更多的react的特性
6.React组件之间如何通信?
组件之间的通信分为父传子通信,子传父通信,非父子组件通信
父传子:在父组件中的子组件标签上绑定自定义属性,挂载传递的数据
子组件中props接收传递过来的数据,直接使用即可
子传父:父组件中子组件标签上绑定一个属性,传递一个方法给子组件
子组件中通过props接收这个方法,直接调用,传递相应的参数就可以
非父子组件的通信:状态提升(中间人模式),context状态树
状态提升:react中的状态提升就是将多个组件需要共享的状态提升到他们最近的父组件当中,在父组件上改变这个状态然后通过props发给子组件
Context状态树:在父组件中我们通过createContext()创建一个空对象,在父组件的最外层我们使用Provider包裹数据,通过value绑定要传递的对象数据
7.说说你对受控组件和非受控组件的理解?应用场景?
受控组件:由react控制的输入表单元素而改变其值的方式叫做受控组件
应用场景:比如表单元素input绑定一个onChange事件,当input状态发生变化时就会触发onChange事件,从而更新组件state
非受控组件:非受控组件就是指,表单数据由DOM本身处理,即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值,在非受控组件中,可以使用ref来从DOM获取表单值
应用场景:在输入框显示用户输入数据,并不更新state,在使用ref获取表单值是更新state
8.说说Connect组件的原理是什么?
connect有四个参数(两个不常用)
第一个:是mapStateToProps这个函数允许我们将store中的数据作为props绑定到数组上,主要原理就是将需要绑定的props作为一个函数传过来,在connect中传递给mapStateToProps一个真实的store数据
第二个:是mapDispatchToProps由于更改数据必须要触发action,因此在这个参数的主要功能就是将action作为props绑定到组件上
执行流程
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件
通过props.store获取祖先Component的store
props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
componentDidMount时,添加事件
this.store.subscribe(this.handleChange),实现页面交互
shouldComponentUpdate时判断是否有避免进行渲染
提升页面性能,并得到nextState
componentWillUnmount时移除注册的事件this.handleChange
9.说说react 中jsx语法糖的本质?
Jsx是JavaScript的语法扩展,或者说是一个类似xml的ECMAScript的语法扩展,它本身没有太多的语法定义,也不期望引入更多的标准
React并不强制使用jsx,jsx通过类似xml的描述方式,描写函数对象,即使在使用了jsx也会在构建过程中,通过babel转换成createElement,所以jsx更像是react的语法糖
10.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
理解
Redux中,中间件就是放在dispatch过程,在分发action进行拦截处理,redux的整个工作流程是,当action发出后reducer立即算出state,整个过程是一个同步的操作,那如果需要支持异步操作,或者支持错误处理,日志监控,这个过程就可以加上中间件,他的一个本质就是一个函数对store.dispatch方法进行了改造,在发出action和执行reducer这两步之间,添加了其他的功能
常用的中间件有:redux-thuck:用于异步操作、redux-logger:用于日志记录
实现原理:中间件都需要通过applyMiddleWares进行注册,作用就是将所有的中间件组成一个数组,依次执行然后作为第二个参数传入create Store中
11.说说AMD、CMD、commonJS模块化规范的区别?
AMD规范就是非同步加载模块,允许指定回调函数,对于依赖的模块AMD是提前执行,requirejs是AMD推崇依赖前置,AMD的api默认是一个当多个用
CMD:对于依赖的模块是延迟执行,CMD严格的区分api
CommonJS用同步的方式加载模块。在服务端,模块文件都存放在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
12.说说package.json中版本号的规则?
软件版本号有四部分组成:
第一部分为主版本号,变化了表示有了一个不兼容上个版本的大更改。
第二部分为次版本号,变化了表示增加了新功能,并且可以向后兼容。
第三部分为修订版本号,变化了表示有bug修复,并且可以向后兼容。
第四部分为日期版本号加希腊字母版本号,希腊字母版本号共有五种,分别为base、alpha、beta 、RC 、 release
13.说说React jsx转换成真实DOM的过程?
使用react.createElement或者是JSX编写的react组件,实际上所有的jsx代码都会转换成react.createElement的内容,babel帮助我们完成了转换的过程,createElement函数对key和ref等特殊的props进行处理,并会获取defaultProps对默认props进行赋值,并且对传入的子节点进行处理,最终构造成一个虚拟dom对象
14.说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
@reduxjs/toolkit是redux官方强烈推荐的一个高效的redux的开发工具集,他的宗旨在称为标准的redux逻辑开发模式redux toolkit最初就是为了帮我们解决现有redux的常见的三个问题而创建的;
由于配置redux store 过于复杂,我们必须添加有关的redux的很多软件包,才可以使用redux
区别
react-redux
react-redux 是的官方 React UI 绑定层,允许您的 React 组件从 Redux 存储中读取数据,并将操作分派到存储以更新状态。
@reduxjs/toolkit
@reduxjs/toolkit 是对 Redux 的二次封装,开箱即用可的一个高效的 Redux 开发工具集,使得创建store、更新store
15.React render方法的原理,在什么时候会触发?
render函数里面可以编写jsx,转化成createElement这种形式,用于生成虚拟dom,最终转换成真实dom,在react中,类组件只要执行了setState方法就一定会触发render函数的执行,函数组件使用useState更改状态不一定导致重新的render组件的props,改变了也不一定触发render函数的执行,但是如果props的值来自于父组件的state在这种情况下,父组件state发生了改变,就会导致子组件的重新渲染,所以一旦指令setState就会执行render,useState会判断当前值有没有发生改变,确定是否去执行render方法,一旦父组件发生渲染,子组件也会发生渲染
16.React性能优化的手段有哪些?
避免使用内联函数;事件的绑定方式;懒加载组件;服务器端渲染;数据的重复使用;组件的复用;
使用React.Memo来缓存组件、使用useMemo缓存大量的计算、使用React.PureComponent , shouldComponentUpdate、避免使用内联对象、避免使用匿名函数、延迟加载不是立即需要的组件、调整CSS而不是强制组件加载和卸载、使用React.Fragment避免添加额外的DOM
17.如何通过原生js实现一个节流函数和防抖函数?
防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。如果n秒内高频事件再次被触发,则重新计算时间。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>防抖</title>
</head>
<body>
<button id="debounce">点我防抖!</button>
<script>
window.onload = function() {
// 1、获取这个按钮,并绑定事件
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
}
// 2、防抖功能函数,接受传参
function debounce(fn) {
// 4、创建一个标记用来存放定时器的返回值
let timeout = null;
return function() {
// 5、每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timeout);
// 6、然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, 1000);
};
}
// 3、需要进行防抖的事件处理
function sayDebounce() {
// ... 有些需要防抖的工作,在这里执行
console.log("防抖成功!");
}
</script>
</body>
</html>
节流:指定时间间隔内只会执行一次任务。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>节流</title>
</head>
<body>
<button id="throttle">点我节流!</button>
<script>
window.onload = function() {
// 1、获取按钮,绑定点击事件
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle));
}
// 2、节流函数体
function throttle(fn) {
// 4、通过闭包保存一个标记
let canRun = true;
return function() {
// 5、在函数开头判断标志是否为 true,不为 true 则中断函数
if(!canRun) {
return;
}
// 6、将 canRun 设置为 false,防止执行之前再被执行
canRun = false;
// 7、定时器
setTimeout( () => {
fn.apply(this, arguments);
// 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
canRun = true;
}, 1000);
};
}
// 3、需要节流的事件
function sayThrottle() {
console.log("节流成功!");
}
</script>
</body>
</html>
18.说说你对koa中洋葱模型的理解?
在 koa 中,中间件被 next() 方法分成了两部分。next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行
在洋葱模型中,每一层相当于一个中间件,用来处理特定的功能,比如错误处理、Session 处理等等。其处理顺序先是 next() 前请求(Request,从外层到内层)然后执行 next() 函数,最后是 next() 后响应(Response,从内层到外层),也就是说每一个中间件都有两次处理时机。
总结
Koa 的洋葱模型指的是以 next() 函数为分割点,先由外到内执行 Request 的逻辑,再由内到外执行 Response 的逻辑。通过洋葱模型,将多个中间件之间通信等变得更加可行和简单。其实现的原理并不是很复杂,主要是 compose 方法。
19.说说如何借助webpack来优化前端性能?
webpack是一个模块打包工具,可以使用webpack管理模块,并分析模块间的依赖关系最终编译输出模块为html、JavaScript和css以及各种静态文件,让开发更加高效
通过webpack优化前端的手段有:
JS代码压缩、CSS代码压缩、Html文件代码压缩、文件大小压缩、图片压缩、Tree Shaking、代码分离、内联 chunk
20.说说你对webSocket的理解?
WebSocket,是一种网络传输协议,位于OSI模型的应用层。可在单个TCP连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅
客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输
而在websocket出现之前,开发实时web应用的方式为轮询,不停地向服务器发送 HTTP 请求,问有没有数据,有数据的话服务器就用响应报文回应。如果轮询的频率比较高,那么就可以近似地实现“实时通信”的效果;轮询的缺点也很明显,反复发送无效查询请求耗费了大量的带宽和 CPU资源
webSocket优点
较少的控制开销:数据包头部协议较小,不同于http每次请求需要携带完整的头部
更强的实时性:相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少
保持创连接状态:创建通信后,可省略状态信息,不同于HTTP每次请求需要携带身份验证
更好的二进制支持:定义了二进制帧,更好处理二进制内容
支持扩展:用户可以扩展websocket协议、实现部分自定义的子协议
更好的压缩效果:Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率
应用场景
基于websocket的事实通信的特点,其存在的应用场景大概有:
弹幕
媒体聊天
协同编辑
基于位置的应用
体育实况更新
股票基金报价实时更新