前端面试题整理

前端面试题整理

1.说说你对react的理解?有哪些特性?

理解

React,用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效,使用虚拟DOM来有效地操作DOM,遵循从高阶组件到低阶组件的单向数据流

特性

	JSX语法
	单向数据绑定
	虚拟DOM
	声明式编程
	Component

特点

	声明式设计
	组件化
	高效
	灵活
	Jsx语法

2.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?

初始化阶段

由ReactDOM.render()触发-----初次渲染
 - constructor()
 - componentWillMount()
 - render()		必须的
 - componentDidMount()   常用,一般在这个钩子做一些初始化,如:开启定时器、发送网络请求、订阅消息

更新阶段

由ReactDOM.render()触发-----初次渲染
由组件内部this.setState()或父组件render触发
 - shouldComponentUpdate()
 - componentWillUpdate()
		forceUpdate()强制更新用得少
 - render()		必须的
 - componentDidUpdate()   

卸载阶段

卸载阶段:
componentWillUnmount()	常用,一般在这个钩子做一些收尾的事情,如:关闭定时器、取消订阅消息

其他生命周期

componentWillReceiveProps:父组件与子组件进行数据传递时触发
getDerivedStateFromProps: static getDerivedStateFromProps(nextProps, prevState),
这是个静态方法,当我们接收到新的属性想去修改state,可以使用getDerivedStateFromProps,
衍生新的state,此方法在更新个挂载阶段都可能会调用
getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState),
这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,
表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,
如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用

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

几种情况下容易造成生命周期的坑

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

  • componentWillMount 在 React 中已被标记弃用,不推荐使用,主要的原因是因为新的异步架构会导致它被多次调用,所以网络请求以及事件绑定应该放到 componentDidMount 中

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

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

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

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

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

避免生命周期中的坑需要做的两件事:

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

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

4.Real diff算法是怎么运作的?

原理

React 采用的是虚拟 DOM (即 VDOM ),每次属性 (props) 和状态 (state) 发生变化的时候,render 函数返回不同的元素树,React 会检测当前返回的元素树和上次渲染的元素树之前的差异,然后针对差异的地方进行更新操作,最后渲染为真实 DOM,这就是整个 Reconciliation 过程,其核心就是进行新旧 DOM 树对比的 diff 算法。

规则

如果有key属性,diff算法会找到相同的key的节点进行比较,如果内容相同就会复用,如果内容不相同就会更新节点;如果没有key属性,会重新渲染所有的新节点和旧节点

5.Real diff算法是怎么运作的?

理解

跟Vue一致,React通过引入Virtual DOM的概念,极大地避免无效的Dom操作,使我们的页面的构建效率提到了极大的提升
而diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处
原理

react中diff算法主要遵循三个层级的策略:

1.tree层级
2.conponent 层级
3.element 层级

tree层级

DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较
只有删除、创建操作,没有移动操作

component层级

如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的

element层级

对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识
提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)
通过key可以准确地发现新旧集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动,更新为新集合中节点的位置

6.说说你对React中虚拟dom的理解?

原理

实际上它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上
创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

实现原理

React虚拟DOM的实现原理,通过JS模拟网页文档节点,生成JS对象树(虚拟DOM),然后再进一步生成真实的DOM树,再绘制到屏幕。如果后面有内容发生改变,React会重新生成一棵全新的虚拟DOM树,再与前面的虚拟DOM树进行比对diff,把差异的部分打包成patch,再应用到真实DOM,然后渲染到屏幕浏览器。

7.说说Real DOM和Virtual DOM的区别?优缺点?

区别

虚拟DOM不会进行重绘和回流,而而真实DOM会频繁重排和重绘

优缺点

真实dom优点:直接操作HTML易用
真实dom缺点:解析速度慢,效率低,内存占用量高;性能差:频繁操作真实DOM,导致重绘、回流
虚拟DOM优点:减少真实dom的频繁更新,减小重绘回流、占用内存少;跨平台
虚拟DOM缺点:页面首次渲染时,由于多一层虚拟DOM的计算,速度比正常慢

8. 说说React jsx转换成真实DOM的过程?

使用react.createElement或者JSX编写react组件,实际上所有的JSX代码都会转换成react.createElement内容,babel帮助我们完成了转换的过程,createElement函数对key和ref等特殊的props进行了处理,并获取defaultProps对默认的props进行赋值,并且对传入的子节点进行处理,最终构造成一个dom对象

9.受控组件和非受控组件的区别?

区别

受控组件:
	受我们控制的组件,组件的状态全程响应外部数据,一般需要初始状态和状态更新事件函数
非受控组件:
	不受我们控住的组件,一般情况是在初始化的时候,接收外部数据,然后自己在内部存储其自身状态,
	当需要时,可以使用ref查询dom并查找其当前值

应用场景

大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理,如果选择非受控组件的话,控制能力较弱,表单数据就由DOM本身处理,但更加方便快捷,代码量少

10.说说react 中jsx语法糖的本质?

Jsx是语法糖,实质是js函数,需要babel来解析,核心函数是React.createElement(tag,{attrbuties},children),参数tag是标签名可以是html标签和组件名,attrbuties参数是标签的属性,children参数是tag的子元素。用来创建一个vnode,最后渲染到页面上

11.说说React中setState执行机制?

(1)钩子函数和合成事件中

在react的生命周期和合成事件中,react仍然处于他的更新机制中,这时isBranchUpdate为true。

按照上述过程,这时无论调用多少次setState,都会不会执行更新,而是将要更新的state存入_pendingStateQueue,将要更新的组件存入dirtyComponent。

当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件didmount后会将isBranchUpdate设置为false。这时将执行之前累积的setState。

(2)异步函数和原生事件中

由执行机制看,setState本身并不是异步的,而是如果在调用setState时,如果react正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象。

在生命周期,根据JS的异步机制,会将异步函数先暂存,等所有同步代码执行完毕后在执行,这时上一次更新过程已经执行完毕,isBranchUpdate被设置为false,根据上面的流程,这时再调用setState即可立即执行更新,拿到更新结果。

(3)partialState合并机制

我们看下流程中_processPendingState的代码,这个函数是用来合并state暂存队列的,最后返回一个合并后的state。

 
  _processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;
 
    if (!queue) {
      return inst.state;
    }
 
    if (replace && queue.length === 1) {
      return queue[0];
    }
 
    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }

    return nextState;
  },

我们只需要关注下面这段代码:

 _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

如果传入的是对象,很明显会被合并成一次:

Object.assign(
  nextState,
  {index: state.index+ 1},
  {index: state.index+ 1}
)

如果传入的是函数,函数的参数preState是前一次合并后的结果,所以计算结果是准确的。

(4)componentDidMount调用setstate

不推荐直接在componentDidMount直接调用setState,由上面的分析:componentDidMount本身处于一次更新中,我们又调用了一次setState,就会在未来再进行一次render,造成不必要的性能浪费,大多数情况可以设置初始值来搞定。

当然在componentDidMount我们可以调用接口,再回调中去修改state,这是正确的做法。

当state初始值依赖dom属性时,在componentDidMount中setState是无法避免的。

(5)componentWillUpdate componentDidUpdate

这两个生命周期中不能调用setState。

由上面的流程图很容易发现,在它们里面调用setState会造成死循环,导致程序崩溃。

(6)推荐使用方式

在调用setState时使用函数传递state值,在回调函数中获取最新更新后的state。

12.setState 是同步,还是异步的?

react18之前:
	setState在不同情况下可以表现为异步或同步。
	在Promise的状态更新、js原生事件、setTimeout、setInterval..中是同步的。
	在react的合成事件中,是异步的。
react18之后:
	setState都会表现为异步(即批处理)。

13.调和阶段setState干了什么?

  1. 代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。
  2. 经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面;
  3. 在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染;
  4. 在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

14.React合成事件的原理?

React合成事件是什么?

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

为什么会有合成事件?

将事件绑定在document - v16/容器元素 - v17统一管理,防止很多事件直接绑定在原生的dom元素上。造成一些不可控的情况
React 想实现一个全浏览器的框架, 为了实现这种目标就需要提供全浏览器一致性的事件系统,以此抹平不同浏览器的差异。

原理

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

15.说说react的事件机制?

(1)react的事件机制

  • react自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。
  • react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来派发执行。
  • 通过这种处理,减少了事件注册的次数,另外react还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。

(2)对合成事件的理解

  • 对原生事件的封装
    react会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象SyntheticFoucsEvent(合成事件对象:SyntheticEvent是react合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的)
  • 不同浏览器事件兼容的处理
    在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理

(3)事件机制的流程

1、事件注册

在组件挂载阶段,根据组件内声明的事件类型-onclick,onchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent。

2、事件存储

完成事件注册后,将 react dom ,事件类型,事件处理函数 fn 放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到 listenerBank(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。

开始事件的存储,在 react 里所有事件的触发都是通过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的
回调,来看下如何存储的 。
react 把所有的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个 map里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn

3、事件执行

  1. 进入统一的事件分发函数(dispatchEvent)
  2. 结合原生事件找到当前节点对应的ReactDOMComponent对象
  3. 开始 事件的合成
    • 根据当前事件类型生成指定的合成对象
    • 封装原生事件和冒泡机制
    • 在 listenerBank事件池中查找事件回调并合成到 event(合成事件结束)
  4. 处理合成事件内的回调事件(事件触发完成 end)

(4)合成事件、原生事件之间的冒泡执行关系

结论:
原生事件阻止冒泡肯定会阻止合成事件的触发。

合成事件的阻止冒泡不会影响原生事件。

原因:

浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。

节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。

16.React组件之间如何通信?

(1)父传子

⽗组件可以通过向⼦组件传 props 的⽅式来实现

(2)子传父

可以采用props+回调的方式

(3)兄弟组件通信

可以通过兄弟节点的共同父节点,由父节点转发信息,实现兄弟间通信

(4)跨层级通信

context状态树

(5)发布者订阅者模式

发布者发布事件,订阅者监听到事件后做出反应,可以通过引⼊ event 模块进⾏

(6)发布者订阅者模式

可以借助 Redux 或 Mobx以及react-redux 等全局状态管理⼯具进⾏通信,它们会维护⼀个全局状态中⼼(Store),并可以根据不同的事件产⽣新的状态

(7)hooks中帮我们封装好的

useContext和useReducer

17.说说你对 useReducer 的理解?

useReducer是React提供的一个高级Hook,它不像useEffect、useState、useRef等必须hook一样,没有它我们也可以正常完成需求的开发,但useReducer可以使我们的代码具有更好的可读性、可维护性、可预测性。

18. Redux 和Vuex的区别?

区别

从实现原理上来说:

Redux 使用的是不可变数据,而Vuex的数据是可变的。
Redux每次都是用新的state替换旧的state,而Vuex是直接修改。
Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,
而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的。

从表现层来说:

vuex定义了state、getter、mutation、action四个对象;redux定义了state、reducer、action。
vuex中state统一存放,方便理解;reduxstate依赖所有reducer的初始值
vuex有getter,目的是快捷得到state;redux没有这层,react-redux mapStateToProps参数做了这个工作。
vuex中mutation只是单纯赋值(很浅的一层);redux中reducer只是单纯设置新state(很浅的一层)。他俩作用
类似,但书写方式不同
vuex中action有较为复杂的异步ajax请求;redux中action中可简单可复杂,简单就直接发送数据对象
({type:xxx, your-data},复杂需要调用异步ajax(依赖redux-thunk插件)。
vuex触发方式有两种commit同步和dispatch异步;redux同步和异步都使用dispatch

共同思想

tate 共享数据
流程一致:定义全局state,触发,修改state
原理相似,通过全局注入store。

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

原理

在类组件和函数组件中,render函数的形式是不同的。
在类组件中render函数指的就是render方法;而在函数组件中,指的就是整个函数组件
在render过程中,React 将新调用的 render函数返回的树与旧版本的树进行比较,这一步是决定如何更新 DOM 的必要步骤,然后进行 diff 比较,更新 DOM树

触发时机

类组件调用 setState 修改状态:
函数组件通过useState hook修改状态:
函数组件通过useState这种形式更新数据,当数组的值不发生改变了,就不会触发render

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

1、使用纯组件;(PureComponent)
2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对 于相同的输入,不重复执行;
3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
4、路由懒加载;
5、使用 React Fragments 避免额外标记;
6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;
9、避免使用内联样式属性;
10、优化 React 中的条件渲染;
11、不要在 render 方法中导出数据;
12、列表渲染的时候加key;
13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
14、类组件中使用immutable对象;

21. koa中洋葱模型?

理解

Koa是一个精简的node框架,被认为是第二代Node框架,其最大的特点就是独特的中间件流程控制,是一个典型的洋葱模型,它的核心工作包括下面两个方面:

  1. 将node原生的req和res封装成为一个context对象。
  2. 基于async/await的中间件洋葱模型机制。

什么是洋葱模型

koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑,这里的request的逻辑,我们可以理解为是next之前的内容,response的逻辑是next函数之后的内容,也可以说每一个中间件都有两次处理时机。洋葱模型的核心原理主要是借助compose方法。

为什么需要洋葱模型

因为很多时候,在一个app里面有很多中间件,有些中间件需要依赖其他中间件的结果,用葱模型可以保证执行顺序,如果没有洋葱模型,执行顺序可能出乎我们的预期

22. 说说webpack中常见的loader?解决了什么问题?

loader是什么

loader 用于对模块的源代码进行转换,在 import 或"加载"模块时预处理文件

webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中

常见的loader

  • style-loader: 将css添加到DOM的内联样式标签style里
  • css-loader :允许将css文件通过require的方式引入,并返回css代码
  • less-loader: 处理less
  • sass-loader: 处理sass
  • postcss-loader: 用postcss来处理CSS
  • autoprefixer-loader: 处理CSS3属性前缀,已被弃用,建议直接使用postcss
  • file-loader: 分发文件到output目录并返回相对路径
  • url-loader: 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
  • html-minify-loader: 压缩HTML
  • babel-loader :用babel来转换ES6文件到ES

解决的问题

在webpack内部中,任何文件都是模块,不仅仅只是js文件

默认情况下,在遇到import或者load加载模块的时候,webpack只支持对js文件打包

像css、sass、png等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解析

23. 前端性能优化的手段有哪些?

  • 减少请求数量
  • 减小资源大小
  • 优化网络连接
  • 优化资源加载
  • 减少重绘回流
  • 使用性能更好的API
  • 构建优化

24.前端清除浮动的四种方式?

  1. 给谁清除浮动,就在其后额外添加一个空白标签 。
    优点:通俗易懂,书写方便。(不推荐使用)
    缺点:添加许多无意义的标签,结构化比较差。
  2. 父级添加overflow方法:可以通过触发BFC的方式,实现清楚浮动效果。
    优点:代码简洁(慎重使用,若该父盒子里还有position定位会引起麻烦)
    缺点:内容增多时候容易造成不会自动换行导致内容被隐藏掉,无法显示需要溢出的元素。
  3. 使用after伪元素清除浮动::after方式为空元素的升级版,好处是不用单独加标签了。(较常用)
    优点:符合闭合浮动思想,结构语义化正确
    缺点:由于IE6-7不支持:after,使用zoom:1,触发hasLayout。
    注意:这个也是给父亲添加 clearfix
  4. 使用before和after双伪元素清除浮动:(较常用)
    注意:是给父亲添加clearfix
    在这里插入图片描述

25.前端跨域的解决方案?

什么是跨域?

在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。

什么是同源策略?

同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:

Cookie、LocalStorage 和 IndexDB 无法读取
DOMJS对象无法获得
AJAX 请求不能发送

9种跨域解决方案

  1. JSONP跨域
  2. 跨域资源共享(CORS)
  3. nginx代理跨域
  4. nodejs中间件代理跨域
  5. document.domain + iframe跨域
  6. location.hash + iframe跨域
  7. window.name + iframe跨域
  8. postMessage跨域
  9. WebSocket协议跨域

小结:
 以上就是9种常见的跨域解决方案,jsonp(只支持get请求,支持老的IE浏览器)适合加载不同域名的js、css,img等静态资源;CORS(支持所有类型的HTTP请求,但浏览器IE10以下不支持)适合做ajax各种跨域请求;Nginx代理跨域和nodejs中间件跨域原理都相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合前后端分离的前端项目调后端接口。document.domain+iframe适合主域名相同,子域名不同的跨域请求。postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10+。

26. 说说如何借助webpack来优化前端性能?

随着前端的项目逐渐扩大,必然会带来的一个问题就是性能
尤其在大型复杂的项目中,前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至奔溃
一般项目在完成后,会通过webpack进行打包,利用webpack对前端项目性能优化是一个十分重要的环节
通过webpack优化前端的手段有:
• JS代码压缩
• CSS代码压缩
• Html文件代码压缩
• 文件大小压缩
• 图片压缩
• Tree Shaking,Tree Shaking 是一个术语,在计算机中表示消除死代码
• 代码分离
• 内联 chunk

27. webSocket?

理解

WebSocket是一种基于TCP的全双工通信协议,在应用层。

为什么需要WebSocket

1、传统上的HTTP协议它是无状态的,服务器不能够识别是哪个客户端发送的请求,不能够保存状态。
2、WebSocket弥补了这一问题,在客户端向服务端发送请求之后,服务器处理请求并返回到客户端,使用WebSocket可以使得服务器主动向浏览器推送消息

WebSocket协议的原理

与服务器进行三次握手,建立TCP连接 向服务器发送HTTP请求,请求中包含WebSocket的版本信息:包括upgrade、connection等等。 服务器处理请求并返回客户端,此时可以进行WebSocket请求了 服务端也可以主动向客户端推送消息了。

WebSocket的优缺点

优点:建立WebSocket连接之后,客户端与服务端交流更方便
客户端只需要向服务端发送一次请求,服务端主动向客户端发送消息
缺点:在服务端的状态不会频繁变化的时候,就不需要使用WebSocket连接了,浪费性能

28. 说说你对自定义hook的理解?

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中;可以理解成Hook就是用来放一些重复代码的函数。

29. 说说你对useMemo 的理解?

使用memo可以帮助我们优化性能,让react没必要执行不必要的函数
由于复杂数据类型的地址可能发生改变,于是传递给子组件的props也会发生变化,这样还是会执行不必要的函数,所以就用到了useMemo这个api

30. 说说你对 useContext 的理解?

什么是Context

context(上下文)可以看成是扩大版的props,它可以将全局的数据通过provider接口传递value给局部的组件,让包围在provider中的局部组件可以获取到全局数据的读写接口
全局变量可以看成是全局的上下文
而上下文则是局部的全局变量,因为只有包围在provider中的局部组件才可以获取到这些全局变量的读写接口

用法

创建context
设置provider并通过value接口传递state
局部组件获取读写接口

31.Proxy对象代理 ?

作用

Proxy代理对象是ES6新增的类,它的作用是当我们希望监听一个对象的相关操作,可以通过创建一个代理对象,之后所有的操作都通过代理对象来完成,这样就可以通过代理对象监听我们想要对原对象进行哪些操作了。

get和set捕获器

get包含三个参数分别是:target目标对象,key被获取的属性key,receiver调用的代理对象。
set包含四个参数分别是:target目标对象,key被获取的属性key,newValue新属性值,receiver调用的代理对象。
当代理对象被访问时会调用get捕获器,被修改时会调用set捕获器。

32.说说你对 Object.defineProperty 的理解?

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
该方法接受三个参数,第一个参数是 obj:要定义属性的对象,第二个参数是 prop:要定义或修改的属性的名称或 Symbol,第三个参数是 descriptor:要定义或修改的属性描述符。

33.说说 Object.defineProperty 与 Proxy 的区别?

1、在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty 方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy 代理的方式实现。
2、Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性。
3、vue中,Proxy在调用时递归,Object.defineProperty在一开始就全部递归,Proxy性能优于Object.defineProperty。
4、对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。
5、数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
6、Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。

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

理解

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

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

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
    从架构角度来看,Fiber 是对 React核心算法(即调和过程)的重写

从编码角度来看,Fiber是 React内部所定义的一种数据结构,它是 Fiber树结构的节点单位,也就是 React 16 新架构下的虚拟DOM

解决的问题

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

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

实现的上述方式的是requestIdleCallback方法

window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

首先 React 中任务切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间,再继续之前 React 未完成的任务,是一种合作式调度。

该实现过程是基于 Fiber节点实现,作为静态的数据结构来说,每个 Fiber 节点对应一个 React element,保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。

作为动态的工作单元来说,每个 Fiber 节点保存了本次更新中该组件改变的状态、要执行的工作。

每个 Fiber 节点有个对应的 React element,多个 Fiber节点根据如下三个属性构建一颗树:

// 指向父级Fiber节点
this.return = null
// 指向子Fiber节点
this.child = null
// 指向右边第一个兄弟Fiber节点
this.sibling = null

35.说说你对事件循环(Event Loop)的理解?Event Loop是什么

即事件循环,是指浏览器或Node的一种解决JavaScript单线程运行时不会阻塞的一种机制,也就是实现异步的原理。作为一种单线程语言,JavaScript本身是没有异步这一说法的,是由其宿主环境提供的。

宏任务与微任务
JavaScript代码运行时,任务分为宏任务和微任务两种,Event Loop也将任务队列分为宏队列和微队列分别管理宏任务和微任务,宏队列和微队列也具备队列特性:先进先出。

  1. 微任务
    Promise中的then、catch、finally
    MutationObserver(监视DOM变动的API)
    Process.nextTick(Node环境)
    
  2. 宏任务
    script中全部代码
    DOM操作
    用户交互操作
    所有的网络请求
    定时器相关的setTimeout、setInterval等
    

事件循环的进程模型

  1. 选择当前要执行的宏任务队列,选择一个最先进任务队列的宏任务,如果没有宏任务,则会跳转至微任务的执行步骤。
  2. 将事件循环的当前运行宏任务设置为已选择的宏任务。
  3. 运行宏任务。
  4. 将事件循环的当前运行任务设置为null。
  5. 将运行完的宏任务从宏任务队列中移除。
  6. 微任务步骤:进入微任务检查点。
  7. 更新界面渲染。
  8. 返回第一步。
  9. 只要主线程空了,就会读取任务队列,这就是js的运行机制,也被称为EventLoop机制。
    在同一个上下文中,总的执行顺序:同步代码→微任务→宏任务

36.说说redux的实现原理是什么,写出核心代码?

  1. 将应用的状态统一放到state中,由store来管理state。
  2. reducer的作用是返回一个新的state去更新store中对用的state。
  3. 按redux的原则,UI层每一次状态的改变都应通过action去触发,action传入对应的reducer 中,reducer返回一个新的state更新store中存放的state,这样就完成了一次状态的更新
  4. subscribe是为store订阅监听函数,这些订阅后的监听函数是在每一次dipatch发起后依次执行
  5. 可以添加中间件对提交的dispatch进行重写

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

理解

中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在Redux中,中间件主要用于增强dispatch函数。

常用的中间件

redux-thunk:通过action返回一个函数进行异步操作
redux-promise :通过action返回一个promise进行异步处理
redux-logger:用于日志记录

实现原理

中间件本身是一个函数,该函数接收一个store参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
需要调用applyMiddleware函数,将函数的返回结果作为createStore的第二或第三个参数。
中间件函数必须返回一个dispatch创建函数

38.如何使用css实现一个三角形?

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title>CSS绘制一个三角形</title>

    <link rel="stylesheet" href="">

    <style>
        .div {

            width: 0px;

            height: 0px;

            border-width: 30px;

            border-style: solid;

            border-color: red transparent transparent transparent;

            overflow: hidden;

            font-size: 0;

            line-height: 0;

        }
    </style>

</head>

<body>

    <div class="div"></div>

</body>

</html>

39.什么是强缓存和协商缓存?区别?

强缓存

当浏览器向服务器发送请求时,资源未过期,服务器直接返还缓存资源,过期重新发送请求

协商缓存

浏览器发送请求时,先发送一次get请求,看服务器缓存资源有没有更新,没有则命中缓存,有则重新发送请求

区别

  1. 如果浏览器命中强缓存,则不需要给服务器发送请求,尽管会返回请求状态码200(from cache);
  2. 协商缓存有服务器决定是否使用缓存,客户端和服务器之间会发生一次通讯,如果命中协商缓存,则服务器返回304。

40.说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?

@reduxjs/toolkit

一个开箱既用的高效redux开发工具,旨在成为标准的readux逻辑开发模式

react-redux

将所有的组件分为了两大类,ui组件以及容器组件,其中容器组件包含着ui组件,容器组件负责和redux进行交互

41.为什么React元素有一个$$typeof属性?

可能以为在编写的是JSX:

<marquee bgcolor="#ffa7c4">hi</marquee>

但是实际上,你调用的是一个函数。

React.createElement(
  /* type */ 'marquee',
  /* props */ { bgcolor: '#ffa7c4' },
  /* children */ 'hi'
)

这个函数返回一个Object对象,叫这个对象为React元素。它告诉React接下来要渲染什么。

编写的组件将组成一个组件树。

{
      type: 'marquee',
      props: {
        bgcolor: '#ffa7c4',
        children: 'hi',
      },
      key: null,
     ref: null,
     typeof: Symbol.for('react.element'),
}

如果你经常使用React,你可能会对type、props、key、ref属性很熟悉,但是,什么是typeof属性?为什么他的属性是一个Symbol值?

从设计上来说,React 元素是一个普通的对象。

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $ $typeof: Symbol.for('react.element'),
}

React有一些有效的例子来支持像我刚刚在上面做的那样编写的普通元素对象。

对于优化编译器,在worker之间传递UI元素或者将JSX与React包解耦是有用的。

但是,如果服务器侧有一个允许用户存储任意JSON对象的漏洞,而客户端代码期待一个字符串,这可能会成为一个问题:

// Server could have a hole that lets user store JSON
let expectedTextButGotJSON = {
      type: 'div',
    props: {
          dangerouslySe: {
        __html: '/* put your exploit here */'
      },
    },
    // ...
  };
  let message = { text: expectedTextButGotJSON };
 
  // Dangerous in React 0.13
  <p>
    {message.text}
  </p>

在这种情况下,React 0.13很容易受到XSS攻击。这种攻击取决于现有的服务器漏洞。
在React 0.14版本,它的修复方法是对每一个React元素使用Symbol来进行标记。

不能把Symbol放在JSON中,因此,即使服务器具有安全漏洞并返回JSON而不是文本,该JSON也不能包含Symbol.for('react.element')。 
React将检查元素的$$typeof属性,如果$$typeof属性丢失或无效,将拒绝处理该元素。

42.说说Connect组件的原理是什么?

首先connect之所以会成功,是因为Provider组件:

在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
接收Redux的store作为props,通过context对象传递给子孙组件上的connect
那connect做了些什么呢?

它真正连接 Redux 和 React,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。

43.说说你对vue中mixin的理解?

什么是指令

指令系统是计算机硬件的语言系统,也叫机器语言,它是系统程序员看到的计算机的主要属性。因此指令系统表征了计算机的基本功能决定了机器所要求的能力

在vue中提供了一套为数据驱动视图更为方便的操作,这些操作被称为指令系统

我们看到的v- 开头的行内属性,都是指令,不同的指令可以完成或实现不同的功能

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令

指令使用的几种方式:

//会实例化一个指令,但这个指令没有参数 
`v-xxx`

// -- 将值传到指令中
`v-xxx="value"`  

// -- 将字符串传入到指令中,如`v-html="'<p>内容</p>'"`
`v-xxx="'string'"` 

// -- 传参数(`arg`),如`v-bind:class="className"`
`v-xxx:arg="value"` 

// -- 使用修饰符(`modifier`)
`v-xxx:arg.modifier="value"` 

如何实现

注册一个自定义指令有全局注册与局部注册

全局注册:

全局注册主要是用过Vue.directive方法进行注册
Vue.directive第一个参数是指令的名字(不需要写上v-前缀),第二个参数可以是对象数据,也可以是一个指令函数

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()  // 页面加载完成之后自动让输入框获取到焦点的小功能
  }
})

局部注册:
局部注册通过在组件options选项中设置directive属性

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
    }
  }
}

自定义指令的钩子函数

  1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
  4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
  5. unbind:只调用一次,指令与元素解绑时调用

应用场景

输入框自动聚焦

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
 // 当被绑定的元素插入到 DOM 中时
 inserted: function (el) {
   // 聚焦元素
   el.focus()
 }
})
<input v-focus>

下拉菜单(点击下拉菜单本身不会隐藏菜单,但是我们现在需要实现隐藏菜单)

Vue.directive('clickoutside', {
  bind(el, binding) {
    function documentHandler(e) {
      if (el.contains(e.target)) {
       return false 
      }
      
      if (binding.expression) {
        binding.value(e)
      }
    }
    
    el.__vueMenuHandler__ = documentHandler
    document.addEventListener('click', el.__vueMenuHandler__)
  },
  unbind(el) {
    document.removeEventListener('click', el.__vueMenuHandler__)
    delete el.__vueMenuHandler__
  }
})
 
new Vue({
  el: '#app',
  data: {
    show: false
  },
  methods: {
    handleHide() {
      this.show = false
    }
  }
})
<div class="main" v-menu="handleHide">
  <button @click="show = !show">点击显示下拉菜单</button>
  <div class="dropdown" v-show="show">
    <div class="item"><a href="#">选项 1</a></div>
    <div class="item"><a href="#">选项 2</a></div>
    <div class="item"><a href="#">选项 3</a></div>
  </div>
</div>

相对时间转换(类似微博、朋友圈发布动态后的相对时间)

<span v-relativeTime="time"></span>
new Vue({
  el: '#app',
  data: {
    time: 1565753400000
  }
})
 
Vue.directive('relativeTime', {
  bind(el, binding) {
    // Time.getFormatTime() 方法,自行补充
    el.innerHTML = Time.getFormatTime(binding.value)
    el.__timeout__ = setInterval(() => {
      el.innerHTML = Time.getFormatTime(binding.value)
    }, 6000)
  },
  unbind(el) {
    clearInterval(el.innerHTML)
    delete el.__timeout__
  }
})

44.说说你对vue中mixin的理解?

mixin是什么

mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类;
mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂。
本质就是一个JS对象,可以包含组件中的任意功能选项,如data、components、methods、creaded、computed以及生命周期函数等等。
只需要将共用的功能以对象的方式传入mixins选项中,当组件使用mixins对象时所有mixins对象的对象都将被混入该组件本身的选项中来。
局部混入

import mixin1 from './mixin1'
export default {
    // mixins:[mixin1]
}

全局混入

Vue.mixin({
  created: function () {
      console.log("全局混入")
    }
})

注意:当组件存在与mixin对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin的选项;但是如果相同的选项为生命周期钩子的时候,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子。
总结:
mixin是一种类,在vue中就是js文件,主要的作用是作为功能模块引用。因为在项目中,可能不同组件会有相同的功能,比如控制元素的显示和隐藏,如果他们的变量和规则也完全相同的话,就可以把这个功能单独提取出来,放在mixin.js中,再引入,就可以实现一样的功能了。引入的方法也分为全局混入和局部混入,局部混入就是在每个组件中引入,全局混入就是在main.js中通过Vue.mixin()引入。

45.for…in循环和for…of循环的区别?

遍历数组时:for…in获取到的是每一项的索引值,而for…of获取到的是每一项的值

遍历对象时:for…in获取到的是每一项的key值(也就是属性名),而for…of不能用于遍历对象,会报错

46.SPA(单页应用)首屏加载速度慢怎么解决?

什么是首屏加载

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

通过DOMContentLoad或者performance来计算出首屏时间

// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {
    console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime
 
// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{
  name: "first-contentful-paint",
  entryType: "paint",
  startTime: 507.80000002123415,
  duration: 0,
};

加载慢的原因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  1. 网络延时问题
  2. 资源文件体积是否过大
  3. 资源是否重复发送请求去加载了
  4. 加载脚本的时候,渲染内容堵塞了

解决方案

  1. 减少入口文件体积
    常用的手段是路由懒加载,只有在解析路由时才会加载组件
const routes = [
{
   path: 'blogs',
   name: 'blogsName',
   component: () => import('./components/BlogsName.js')
}
  1. 静态资源本地缓存
    后端返回资源: 采用HTTP缓存
    前端合理利用:localStorage
    CDN静态资源缓存 react, react-dom, react-router-dom, axios

  2. UI框架按需加载
    在日常使用的UI框架,例如element-UI, antd, 我们要按需引入
    import { Button } from ‘antd’

  3. 避免组件重复打包
    假如A.js 文件是一个常用的库,现在有多个路由使用A.js文件,这样就造成重复下载
    解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置minChunks:2,
    minChunks为2会把使用2次及以上的包抽离出来,放进公共依赖文件中避免了重复加载组件。

  4. 图片资源压缩
    图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素
    对于所有的图片资源,我们可以进行适当的压缩
    对于页面上使用的icon,可以使用在线字体图标,或者雪碧图,将众多的小图标合并到一张图中,用以减轻http请求的压力

  5. 开启GZip压缩
    拆完包后,我们再用gzip做一下压缩,安装compression-webpack-plugin

cnmp i compression-webpack-plugin -D

webpack中配置安装

const CompressionWebpackPlugin = require('compression-webpack-plugin')
const productGzipExtensions = ['js', 'css']
// 配置compression-webpack-plugin压缩
new CompressionWebpackPlugin({
	algorithm: 'gzip',
	test: 'new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$')',
	threshold: 10240,
	minRation: 0.8
})

  1. 使用SSR
    SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器

从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染

47.大文件如何做断点续传?

是什么

当上传大文件时,以下几个变量会影响我们的用户体验

  • 服务器处理数据的能力
  • 请求超时
  • 网络波动

上传时间会变长,高频次文件上传失败,失败后又需要重新上传等等

为了解决上述问题,我们需要对大文件上传单独处理

这里涉及到分片上传及断点续传两个概念

分片上传

分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传

上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件

大致流程如下:

  1. 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
  2. 初始化一个分片上传任务,返回本次分片上传唯一标识;
  3. 按照一定的策略(串行或并行)发送各个分片数据块;
  4. 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件

断点续传

断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度

一般实现方式有两种:

  • 服务器端返回,告知从哪开始
  • 浏览器端自行处理
    上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可

如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可

实现思路

拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕

使用场景

  • 大文件加速上传:当文件大小超过预期大小时,使用分片上传可实现并行上传多个 Part, 以加快上传速度
  • 网络环境较差:建议使用分片上传。当出现上传失败的时候,仅需重传失败的Part
  • 流式上传:可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见

48.谈谈你对BFC的理解?

BFC是什么?

官方定义:BFC(Block Formatting Context)块格式化上下文, 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

简单的理解:BFC就是一个块级容器,它会隔离外部,让盒子里面的元素不影响外面的元素,也就是在搭建页面的时候,不影响外面的布局。

BFC特性

  1. 属于同一个BFC的两个相邻容器的上下margin会重叠(重点)

  2. 计算BFC高度时浮动元素也参于计算(重点)

  3. BFC的区域不会与浮动容器发生重叠(重点)

  4. BFC内的容器在垂直方向依次排列

  5. 元素的margin-left与其包含块的border-left相接触

  6. BFC是独立容器,容器内部元素不会影响容器外部元素

BFC的触发条件

  • 根元素,即HTML元素
  • 浮动元素:float值为left、right
  • overflow值不为 visible,为 auto、scroll、hidden
  • display的值为inline-block、inltable-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid
  • position的值为absolute或fixed

使用BFC解决问题

目前来说,最常见的是通过 overflow: hidden 来构建 BFC。一般情况下,它的副作用最小。但如果元素下有超出盒子范围的内容,会被截掉

  1. 解决边距重叠问题:
    因为处于同一个BFC下,我们只需要在其中一个块级元素外面包裹一层容器,使两个块级元素不属于同一层级,并且触发BFC(重点,因为不触发BFC,即使裹了容器,还是会发生重叠),这样该容器里面的元素就不会影响外面的元素,margin就不会发生重叠
    <style>
        .wrap {
            overflow: hidden;// 新的BFC
        }
        p {
            color: #f55;
            background: #fcc;
            width: 200px;
            line-height: 100px;
            text-align:center;
            margin: 20px;
        }
    </style>
    <body>
        <p>Haha</p >
        <div class="wrap">
            <p>Hehe</p >
        </div>
    </body>
    
  2. 解决高度坍塌问题
  3. 解决相邻盒子浮动区域重叠问题
    因为加了浮动的盒子不占据空间,那么与其相邻的盒子就会与浮动的盒子重叠,为了解决这个问题,其实有两种方法:
    • 给相邻的盒子也加上浮动
    • 给相邻的盒子设置overflow:hidden
      两者的区别在于,float对齐方式是,两个盒子的外边距对齐,overflow是内容对齐左盒子的顶部

49.说说TCP为什么需要三次握手和四次挥手?

三次握手

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

四次挥手

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
  • 服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。

50.说说javascript内存泄漏的几种情况?

内存泄漏是什么

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存

对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃

垃圾回收机制

Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

通常情况下有两种实现方式:

  • 标记清除
  • 引用计数

意外的全局变量

function foo(arg) {
    bar = "this is a hidden global variable";
}

另一种意外的全局变量可能由 this 创建:

function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();

上述使用严格模式,可以避免意外的全局变量

定时器也常会造成内存泄露

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

如果id为Node的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource的引用,定时器外面的someResource也不会被释放

闭包,维持函数内局部变量,使其得不到释放

function bindEvent() {
  var obj = document.createElement('XXX');
  var unused = function () {
    console.log(obj, '闭包内引用obj obj不会被释放');
  };
  obj = null; // 解决方法
}

没有清理对DOM元素的引用同样造成内存泄露

const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用

包括使用事件监听addEventListener监听的时候,在不监听的情况下使用removeEventListener取消对事件监听

51. 防抖与节流?

js防抖

特点

  1. 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间(非常短的时间);
  2. 当事件密集触发时,函数的触发会被频繁的推迟;
  3. 只有等待了一段时间也没有事件触发,才会真正的执行响应函数;

应用场景

  • 输入框中频繁的输入内容,搜索或者提交信息;
  • 频繁的点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定操作;
  • 用户缩放浏览器的resize事件;

js节流

  1. 当事件触发时,会执行这个事件的响应函数;
  2. 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;
  3. 不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的;

特点

  • 游戏中的一些设计–王者荣耀 英雄的普攻;
  • 监听页面的滚动事件;
    -鼠标移动事件;
  • 用户频繁点击按钮操作;

52.JS数据类型判断都有哪几种方式?它们的区别是什么?

JavaScript的五种数据判断类型方法:

typeof
instanceof
constructor
Object.prototype.toString.call()
jquery.type()

区别

一、typeof方法:

  1. 可以判断数据类型,它返回表示数据类型的字符串(返回结果只能包括number,boolean,string,function,object,undefined);
  2. 可以使用typeof判断变量是否存在(如if(typeof a!=“undefined”){…});
  3. Typeof 运算符的问题是无论引用的对象是什么类型 它都返回object

二、instanceof方法:
一般用来检测引用数据类型,表达式为:A instanceof B,判断A是否是B的实例,如果 A 是 B 的实例,则返回 true,否则返回 false,由构造类型判断出数据类型

三、constructor方法:
constructor是prototype对象上的属性,指向构造函数,

四、Object.prototype.toString 方法:
用来检测对象类型

五、无敌万能的方法:jquery.type()
如果对象是undefined或null,则返回相应的“undefined”或“null”。

53.原生js如何实现上拉加载下拉刷新?

上拉加载

  • clientHeight:不包含边框的元素可视区高度

  • scrollTop:元素滚动时卷上去的距离

  • scrollHeight: 元素实际高度,包含卷上去的高度

  • clientHeight + scrollTop <= scrollHeight - 触底的指定距离

上拉加载的原理:
通过监听元素的滚动事件(scroll)判断元素是否滚动到了距离底部指定距离时触发加载数据

// An highlighted block
// 滚动容器
    const container = document.querySelector('.container');
    // 监听滚动事件
    container.addEventListener('scroll', _.debounce(function (e) {
        // 当元素的可视高度+滚入的距离>=元素真实高度-触底距离时,触发加载更多
        if ((this.clientHeight + this.scrollTop) >= this.scrollHeight - 50) {
            setTimeout(() => {
                // 这里是一个异步加载数据的操作
                console.log('加载更多')
            }, 1000);
        }
    }, 700))

下拉刷新

移动端触屏事件:

  • touchstart(手指按下的一瞬间)
  • touchmove(手指在屏幕上移时)
  • touchend(手指松开时)
  • 手指在页面上的坐标: pageX,pageY

下拉刷新的原理:

  1. 在手指按下时(touchstart)记录手指的按下位置
  2. 在手指下滑时(touchmove)计算手指的坐标离手指按下时初始位置的差值得出下滑的距离,让容器顺应手指下滑的方向移动(translateY)对应差值的距离,对应的给一个允许用户下滑的最大距离,避免页面下拉过长.
  3. 在手指松开时(touchend)判断下滑的差值是否达到预期的值来进行对应的刷新数据和回弹loading.
html部分
<section class="container">
    <section class="loading">
            <span>下拉刷新</span>
        </section>
        <section class="list">
    </section>
</section>
css部分
 .container {
	     /* 容器原始位置向上移动60px,隐藏掉loading盒子,下拉时才显示出来 */
	      position: relative;
	      top: -100px;
}
js部分
// 滚动容器
    const container = document.querySelector('.container');
    // loading文字容器
    const span = container.querySelector('span');
    let startPosition = 0;// 下拉的开始位置
    let distance = 0;// 下拉距离的差值
    // 手指按下时
    container.addEventListener('touchstart', function (e) {
        // 在回弹后的下一次下拉按下时重置loading文本
        span.textContent = '下拉刷新';
        // 记录开始位置
        startPosition = e.touches[0].pageY;
    })
    // 手指移动时
    container.addEventListener('touchmove', function (e) {
        // 计算下拉差值
        const currentPosition = e.touches[0].pageY;
        // 计算下拉后离开始位置的差值
        distance = currentPosition - startPosition;
        // 如果下拉差值达到,则提示可以松手了 这个达到的具体值这里是取的下拉出来的区域高度
        if (distance > 100) {// 案例以100为临界值,超过了100的距离就提示释放刷新
            span.textContent = '释放刷新';
        }
        // 限制下滑的最大值为120,超过就不再下滑
        if (distance < 120) {
            // 容器的这个下滑是瞬时的 取消过渡效果
            this.style.transition = 'transform 0s';
            this.style.transform = `translateY(${distance}px)`
        }
    })
    // 手指松开时
    container.addEventListener('touchend', function (e) {
        // 回弹的动作可以给个1s的过渡效果
        this.style.transition = 'transform 1s';
        // 如果下拉差值并没有达到 则直接回弹
        if (distance > 0 && distance < 100) {
            this.style.transform = `translateY(0px)`
            return;
        }
        if (distance > 100) {
            // 下拉差值达到了就显示刷新中,并暂时定格在这个位置
            this.style.transform = `translateY(100px)`;
            span.textContent = '刷新中';
            // 等数据回来后显示刷新成功1s然后再回弹 到这里本次整个下拉执行完毕
            setTimeout(() => {// setTimeout模拟异步请求 真实开发这里是一个promise请求
                span.textContent = '刷新成功';
                // 这个setTimeout让刷新成功显示一秒后再回弹
                setTimeout(() => {
                    this.style.transform = `translateY(0px)`
                }, 1000)
            }, 2000);
        }
        // 一次下拉结束后重置差值
        distance = 0;
    })

54.说说css像素、设备像素、设备独立像素、dpr、ppi 之间的区别?

介绍

CSS像素:
CSS像素(css pixel, px): 适用于web编程,在 CSS 中以 px 为后缀,是一个长度单位

在 CSS 规范中,长度单位可以分为两类,绝对单位以及相对单位

px是一个相对单位,相对的是设备像素(device pixel)

一般情况,页面缩放比为1,1个CSS像素等于1个设备独立像素

CSS像素又具有两个方面的相对性:

  • 在同一个设备上,每1个 CSS 像素所代表的设备像素是可以变化的(比如调整屏幕的分辨率)
  • 在不同的设备之间,每1个 CSS 像素所代表的设备像素是可以变化的(比如两个不同型号的手机)

设备像素:
设备像素(device pixels),又称为物理像素
指设备能控制显示的最小物理单位,不一定是一个小正方形区块,也没有标准的宽高,只是用于显示丰富色彩的一个“点”而已,从屏幕在工厂生产出的那天起,它上面设备像素点就固定不变了,单位为pt

设备独立像素:
设备独立像素(Device Independent Pixel):与设备无关的逻辑像素,代表可以通过程序控制使用的虚拟像素,是一个总体概念,包括了CSS像素

dpr:
dpr(device pixel ratio),设备像素比,代表设备独立像素到设备像素的转换关系,在JavaScript中可以通过 window.devicePixelRatio 获取
DPR=设备像素 / 设备独立像素
当dpr为3,那么1px的CSS像素宽度对应3px的物理像素的宽度,1px的CSS像素高度对应3px的物理像素高度

ppi:
ppi (pixel per inch),每英寸像素,表示每英寸所包含的像素点数目,更确切的说法应该是像素密度。数值越高,说明屏幕能以更高密度显示图像
在这里插入图片描述

总结

无缩放情况下,1个CSS像素等于1个设备独立像素

设备像素由屏幕生产之后就不发生改变,而设备独立像素是一个虚拟单位会发生改变

PC端中,1个设备独立像素 = 1个设备像素 (在100%,未缩放的情况下)

在移动端中,标准屏幕(160ppi)下 1个设备独立像素 = 1个设备像素

设备像素比(dpr) = 设备像素 / 设备独立像素

每英寸像素(ppi),值越大,图像越清晰

55.说说你对栈、队列的理解?应用场景?

栈(stack)又名堆栈,它是一种运算受限的线性表,限定仅在表尾进行插入和删除操作的线性表
表尾这一端被称为栈顶,相反地另一端被称为栈底,向栈顶插入元素被称为进栈、入栈、压栈,从栈顶删除元素又称作出栈

队列

跟栈十分相似,队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作

进行插入操作的端称为队尾,进行删除操作的端称为队头,当队列中没有元素时,称为空队列

应用场景

栈:

借助栈先进后出的特点可以实现:
字符串倒序输出
简单实现一个逆序数处的功能,首先把所有元素依次入栈,然后把所有元素出栈并输出

队列:

当我们需要按照一定的顺序来处理数据,而该数据的数据量在不断地变化的时候,借助队列先进先出的特点
队列的使用广泛应用在广度优先搜索种,例如层次遍历一个二叉树的节点值

56.说说你对git rebase 和git merge的理解?区别?

是什么

在使用 git 进行版本管理的项目中,当完成一个特性的开发并将其合并到 master 分支时,会有两种方式:

  • git merge
  • git rebase

git rebase 与 git merge都有相同的作用,都是将一个分支的提交合并到另一分支上,但是在原理上却不相同

git merge:
将当前分支合并到指定分支,命令用法如下:

git merge xxx

git rebase:
将当前分支移植到指定分支或指定commit之上,用法如下:

git rebase -i <commit>

常见的参数有–continue,用于解决冲突之后,继续执行rebase

git rebase --continue

区别

merge:
通过merge合并分支会新增一个merge commit,然后将两个分支的历史联系起来
其实是一种非破坏性的操作,对现有分支不会以任何方式被更改,但是会导致历史记录相对复杂
rebase:
rebase会将整个分支移动到另一个分支上,有效地整合了所有分支上的提交
主要的好处是历史记录更加清晰,是在原有提交的基础上将差异内容反映进去,消除了 git merge所需的不必要的合并提交

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值