阿里百度面经总结(一年经验)

  • 备注: 转载自掘金,原文请点击左下角查看原文

  • 原文: https://juejin.im/post/5befeb5051882511a8527dbe

  • 作者: 麦乐

人家都说,前端需要每年定期出来面面试,衡量一下自己当前的技术水平以及价值,本人 17 年 7 月份,毕业到现在都没出来试过,也没很想换工作,就出来试试,看看自己水平咋样。

以下为我现场面试时候的一些回答,部分因人而异的问题我就不回答了,回答的都为参考答案,也有部分错误的地方或者不好的地方,有更好的答案的可以在评论区评论。

百度 WEB 前端工程师 连续五面 全程 3 约个小时

一面

先完成笔试题

  1. 实现一个函数,判断输入是不是回文字符串。

function run(input) {
  if (typeof input !== 'string') return false;
  return input.split('').reverse().join('') === input;
}
  1. 两种以上方式实现已知或者未知宽度的垂直水平居中。



.wrapper {
  position: relative;
  .box {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100px;
    height: 100px;
    margin: -50px 0 0 -50px;
  }
}


.wrapper {
  position: relative;
  .box {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
}


.wrapper {
  .box {
    display: flex;
    justify-content:center;
    align-items: center;
    height: 100px;
  }
}


.wrapper {
  display: table;
  .box {
    display: table-cell;
    vertical-align: middle;
  }
}
  1. 实现效果,点击容器内的图标,图标边框变成 border 1px solid red,点击空白处重置。

const box = document.getElementById('box');
function isIcon(target) {
  return target.className.includes('icon');
}

box.onclick = function(e) {
  e.stopPropagation();
  const target = e.target;
  if (isIcon(target)) {
    target.style.border = '1px solid red';
  }
}
const doc = document;
doc.onclick = function(e) {
  const children = box.children;
  for(let i = 0; i < children.length; i++) {
    if (isIcon(children[i])) {
      children[i].style.border = 'none';
    }
  }
}
  1. 请简单实现双向数据绑定 mvvm。

<input id="input"/>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
  set(value) {
    input.value = value;
    this.value = value;
  }
});
input.onchange = function(e) {
  data.text = e.target.value;
}
  1. 实现 Storage,使得该对象为单例,并对 localStorage 进行封装设置值 setItem(key,value)和 getItem(key)

var instance = null;
class Storage {
  static getInstance() {
    if (!instance) {
      instance = new Storage();
    }
    return instance;
  }
  setItem = (key, value) => localStorage.setItem(key, value),
  getItem = key => localStorage.getItem(key)
}

Q1 你的技术栈主要是 react,那你说说你用 react 有什么坑点?

1、JSX 做表达式判断时候,需要强转为 boolean 类型,如:

render() {
  const b = 0;
  return <div>
    {
      !!b && <div>这是一段文本</div>
    }
  </div>
}

如果不使用 !!b 进行强转数据类型,会在页面里面输出 0。

2、尽量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃。(实际不是 componentWillReviceProps 会无限重渲染,而是 componentDidUpdate)

3、给组件添加 ref 时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的 prop 处理,让 ref 属性接受到新函数的时候,react 内部会先清空 ref,也就是会以 null 为回调参数先执行一次 ref 这个 props,然后在以该组件的实例执行一次 ref,所以用匿名函数做 ref 的时候,有的时候去 ref 赋值后的属性会取到 null。详情见[2]

4、遍历子节点的时候,不要用 index 作为组件的 key 进行传入。

Q2 我现在有一个 button,要用 react 在上面绑定点击事件,要怎么做?

class Demo {
  render() {
    return <button onClick={(e) => {
      alert('我点击了按钮')
    }}>
      按钮
    </button>
  }
}

Q3 接上一个问题,你觉得你这样设置点击事件会有什么问题吗?

由于 onClick 使用的是匿名函数,所有每次重渲染的时候,会把该 onClick 当做一个新的 prop 来处理,会将内部缓存的 onClick 事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降(个人认为)。

修改

class Demo {

  onClick = (e) => {
    alert('我点击了按钮')
  }

  render() {
    return <button onClick={this.onClick}>
      按钮
    </button>
  }
}

当然你在内部声明的不是箭头函数,然后你可能需要在设置 onClick 的时候使用 bind 绑定上下文,这样的效果和先前的使用匿名函数差不多,因为 bind 会返回新的函数,也会被 react 认为是一个新的 prop。

Q4 你说说 event loop 吧

首先,js 是单线程的,主要的任务是处理用户的交互,而用户的交互无非就是响应 DOM 的增删改,使用事件队列的形式,一次事件循环只处理一个事件响应,使得脚本执行相对连续,所以有了事件队列,用来储存待执行的事件,那么事件队列的事件从哪里被 push 进来的呢。那就是另外一个线程叫事件触发线程做的事情了,他的作用主要是在定时触发器线程、异步 HTTP 请求线程满足特定条件下的回调函数 push 到事件队列中,等待 js 引擎空闲的时候去执行,当然 js 引擎执行过程中有优先级之分,首先 js 引擎在一次事件循环中,会先执行 js 线程的主任务,然后会去查找是否有微任务 microtask(promise),如果有那就优先执行微任务,如果没有,在去查找宏任务 macrotask(setTimeout、setInterval)进行执行。

Q5 说说事件流吧

事件流分为两种,捕获事件流和冒泡事件流。

捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。

冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。

DOM 事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段。

Q6 我现在有一个进度条,进度条中间有一串文字,当我的进度条覆盖了文字之后,文字要与进度条反色,怎么实现?

。。。当时我给的是 js 的方案,在进度条宽度变化的时候,计算盖过每一个文字的 50%,如果超过,设置文字相反颜色。

当然 css 也有对应的方案,也就是 mix-blend-mode,我并没有接触过。

对应 html 也有对应方案,也就设置两个相同位置但是颜色相反的 dom 结构在重叠在一起,顶层覆盖底层,最顶层的进度条取 overflow 为 hidden,其宽度就为进度。

二面

Q1 你为什么要离开上一家公司?

-

Q2 你觉得理想的前端地位是什么?

-

Q3 那你意识到问题所在,你又尝试过解决问题吗?

-

三面

Q1 说一下你上一家公司的一个整体开发流程吧

-

Q2 react 的虚拟 dom 是怎么实现的

首先说说为什么要使用 Virturl DOM,因为操作真实 DOM 的耗费的性能代价太高,所以 react 内部使用 js 实现了一套 dom 结构,在每次操作在和真实 dom 之前,使用实现好的 diff 算法,对虚拟 dom 进行比较,递归找出有变化的 dom 节点,然后对其进行更新操作。为了实现虚拟 DOM,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是 prop,每次进行 diff 的时候,react 会先比较该节点类型,假如节点类型不一样,那么 react 会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较 prop 是否有更新,假如有 prop 不一样,那么 react 会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点。

Q3 react 的渲染过程中,兄弟节点之间是怎么处理的?也就是 key 值不一样的时候。

通常我们输出节点的时候都是 map 一个数组然后返回一个 ReactNode,为了方便 react 内部进行优化,我们必须给每一个 reactNode 添加 key,这个 key prop 在设计值处不是给开发者用的,而是给 react 用的,大概的作用就是给每一个 reactNode 添加一个身份标识,方便 react 进行识别,在重渲染过程中,如果 key 一样,若组件属性有所变化,则 react 只更新组件对应的属性;没有变化则不更新,如果 key 不一样,则 react 先销毁该组件,然后重新创建该组件。

Q4 我现在有一个数组[1,2,3,4],请实现算法,得到这个数组的全排列的数组,如[2,1,3,4],[2,1,4,3]。。。。你这个算法的时间复杂度是多少

这个我没写出来,大概给了个思路,将每一个数组拆除俩个小数组进行求它的全排列,然后得到的结果互相之间又进行全排列,然后把最后的结果连接起来。。。

感兴趣的同学见数组全排列[3]

Q5 我现在有一个背包,容量为 m,然后有 n 个货物,重量分别为 w1,w2,w3...wn,每个货物的价值是 v1,v2,v3...vn,w 和 v 没有任何关系,请求背包能装下的最大价值。

这个我也没写出来,也给了个思路,首先使用 Q4 的方法得到货物重量数组的全组合(包括拆分成小数组的全组合),然后计算每一个组合的价值,并进行排序,然后遍历数组,找到价值较高切刚好能装进背包 m 的组合。

本题动态规划面试题,感兴趣的同学请自行百度或者谷歌。

四面

Q1 请说一下你的上一家公司的研发发布流程。

-

Q2 你说一下 webpack 的一些 plugin,怎么使用 webpack 对项目进行优化。

正好最近在做 webpack 构建优化和性能优化的事儿,当时吹了大概 15~20 分钟吧,插件请见webpack 插件归纳总结[4]

构建优化

1、减少编译体积 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。

2、并行编译 happypack、thread-loader、uglifyjsWebpackPlugin 开启并行

3、缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin 开启缓存、babel-loader 开启缓存

4、预编译 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin

性能优化

1、减少编译体积 Tree-shaking、Scope Hositing。

2、hash 缓存 webpack-md5-plugin

3、拆包 splitChunksPlugin、import()、require.ensure

Q3 es6 class 的 new 实例和 es5 的 new 实例有什么区别

这个我觉得是一样的(当时因为很少看 babel 编译之后的结果),面试官说不一样。。。后来我看了一下 babel 的编译结果,发现只是类的方法声明的过程不一样而已,最后 new 的结果是一样的。。。具体答案现在我也不知道。。。

Q4 看你简历上写了 canvas,你说一下为什么 canvas 的图片为什么过有跨域问题。

canvas 图片为什么跨域我不知道,至今没查出来,也差不多,大概跨域原因和浏览器跨域的原因是一样的吧。

Q5 我现在有一个 canvas,上面随机布着一些黑块,请实现方法,计算 canvas 上有多少个黑块。

使用 getImageData 获取像素数组,然后遍历数组,把在遍历节点的过程中,查看节点上下左右的像素颜色是否相同,如果相同,然后设置标识,最后 groupBy 一下所有像素。(这是我当时的方案)

其他更好的答案见地址[5]

Q6 请手写实现一个 promise

这个就不写了,详情见promise 实现原理[6]

注:四面是一个超级可爱的小姐姐,电脑给我让我写完之后,我说我写得差不多了,然后电脑给她,然后她竟然默默的在看我的代码,尝试寻找我的思路,也没有问我实现思路是啥,然后我就问她,你不应该是让我给你解释我的代码思路吗。。。你竟然在尝试寻找我的思路,我自己都不知道我自己是思路是啥。。。然后我两都笑了,哈哈哈。最后结束的时候我说我午饭还没吃,她还叫了另外一个小哥哥先带了下去吃饭,真是一个善良的小姐姐,非常感谢。

五面

Q1 你说一下你的技术有什么特点

-

Q2 说一下你觉得你最得意的一个项目?你这个项目有什么缺陷,弊端吗?

-

Q3 现在有那么一个团队,假如让你来做技术架构,你会怎么做?

考虑到团队每一个前端的技术栈可能不一致,这个时候我可能选择微前端架构,让每个人负责的模块可以单独开发,单独部署,单独回滚,不依赖于其他项目模块,在尽可能的情况下节约团队成员之间的学习成本,当然这肯定也有缺点,那就是每个模块都需要一个前端项目,单独部署,单独回滚无疑也加大了运维成本。

Q4 说一下你上一家公司的主要业务流程,你参与到其中了吗?

-

杭州有赞

一面 WEB 前端工程师 电话面 全程 43 分钟

Q1 自我介绍

-

Q2 说说从输入 URL 到看到页面发生的全过程,越详细越好。

  1. 首先浏览器主进程接管,开了一个下载线程。

  2. 然后进行 HTTP 请求(DNS 查询、IP 寻址等等),中间会有三次捂手,等待响应,开始下载响应报文。

  3. 将下载完的内容转交给 Renderer 进程管理。

  4. Renderer 进程开始解析 css rule tree 和 dom tree,这两个过程是并行的,所以一般我会把 link 标签放在页面顶部。

  5. 解析绘制过程中,当浏览器遇到 link 标签或者 script、img 等标签,浏览器会去下载这些内容,遇到时候缓存的使用缓存,不适用缓存的重新下载资源。

  6. css rule tree 和 dom tree 生成完了之后,开始合成 render tree,这个时候浏览器会进行 layout,开始计算每一个节点的位置,然后进行绘制。

  7. 绘制结束后,关闭 TCP 连接,过程有四次挥手。

Q3 你刚刚说了三次握手,四次挥手,那你描述一下?

本人对计算机网络的这些概念一直不是很熟悉,所以这个问题回答不会,这里 mark 下文章,感兴趣的同学查看地址[7]

Q4 刚刚 Q2 中说的 CSS 和 JS 的位置会影响页面效率,为什么?

css 在加载过程中不会影响到 DOM 树的生成,但是会影响到 Render 树的生成,进而影响到 layout,所以一般来说,style 的 link 标签需要尽量放在 head 里面,因为在解析 DOM 树的时候是自上而下的,而 css 样式又是通过异步加载的,这样的话,解析 DOM 树下的 body 节点和加载 css 样式能尽可能的并行,加快 Render 树的生成的速度。

js 脚本应该放在底部,原因在于 js 线程与 GUI 渲染线程是互斥的关系,如果 js 放在首部,当下载执行 js 的时候,会影响渲染行程绘制页面,js 的作用主要是处理交互,而交互必须得先让页面呈现才能进行,所以为了保证用户体验,尽量让页面先绘制出来。

Q5 现在有一个函数 A 和函数 B,请你实现 B 继承 A


function B(){}
function A(){}
B.prototype = new A();


function A(){}
function B(){
  A.call(this);
}


function B(){}
function A(){}
B.prototype = new A();

function B(){
  A.call(this);
}

Q6 刚刚你在 Q5 中说的几种继承的方式,分别说说他们的优缺点

方式 1:简单易懂,但是无法实现多继承,父类新增原型方法/原型属性,子类都能访问到

方式 2:可以实现多继承,但是只能继承父类的实例属性和方法,不能继承原型属性/方法

方式 3:可以继承实例属性/方法,也可以继承原型属性/方法,但是示例了两个 A 的构造函数

Q7 说说 CSS 中几种垂直水平居中的方式

参考前面百度一面笔试题 Q2

Q8 Q7 中说的 flex 布局,垂直水平居中必须知道宽度吗?

是的,必须知道高度( 脑子进水了回答了必须知道,其实答案是不需要知道高度的)

Q9 描述一下 this

this,函数执行的上下文,可以通过 apply,call,bind 改变 this 的指向。对于匿名函数或者直接调用的函数来说,this 指向全局上下文(浏览器为 window,nodejs 为 global),剩下的函数调用,那就是谁调用它,this 就指向谁。当然还有 es6 的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this 就指向哪里。

Q10 说一下浏览器的缓存机制

浏览器缓存机制有两种,一种为强缓存,一种为协商缓存。

对于强缓存,浏览器在第一次请求的时候,会直接下载资源,然后缓存在本地,第二次请求的时候,直接使用缓存。

对于协商缓存,第一次请求缓存且保存缓存标识与时间,重复请求向服务器发送缓存标识和最后缓存时间,服务端进行校验,如果失效则使用缓存。

强缓存方案

Exprires:服务端的响应头,第一次请求的时候,告诉客户端,该资源什么时候会过期。Exprires 的缺陷是必须保证服务端时间和客户端时间严格同步。

Cache-control:max-age,表示该资源多少时间后过期,解决了客户端和服务端时间必须同步的问题,

协商缓存方案

If-None-Match/ETag:缓存标识,对比缓存时使用它来标识一个缓存,第一次请求的时候,服务端会返回该标识给客户端,客户端在第二次请求的时候会带上该标识与服务端进行对比并返回 If-None-Match 标识是否表示匹配。

Last-modified/If-Modified-Since:第一次请求的时候服务端返回 Last-modified 表明请求的资源上次的修改时间,第二次请求的时候客户端带上请求头 If-Modified-Since,表示资源上次的修改时间,服务端拿到这两个字段进行对比。

Q11 ETag 是这个字符串是怎么生成的?

没答出来,我当时猜是根据文件内容或者最后修改时间进行的加密算法。其实官方没有明确指定生成 ETag 值的方法。

通常,使用内容的散列,最后修改时间戳的哈希值,或简单地使用版本号。

Q12 现在要你完成一个 Dialog 组件,说说你设计的思路?它应该有什么功能?

  1. 该组件需要提供 hook 指定渲染位置,默认渲染在 body 下面。

  2. 然后改组件可以指定外层样式,如宽度等

  3. 组件外层还需要一层 mask 来遮住底层内容,点击 mask 可以执行传进来的 onCancel 函数关闭 Dialog。

  4. 另外组件是可控的,需要外层传入 visible 表示是否可见。

  5. 然后 Dialog 可能需要自定义头 head 和底部 footer,默认有头部和底部,底部有一个确认按钮和取消按钮,确认按钮会执行外部传进来的 onOk 事件,然后取消按钮会执行外部传进来的 onCancel 事件。

  6. 当组件的 visible 为 true 时候,设置 body 的 overflow 为 hidden,隐藏 body 的滚动条,反之显示滚动条。

  7. 组件高度可能大于页面高度,组件内部需要滚动条。

  8. 只有组件的 visible 有变化且为 ture 时候,才重渲染组件内的所有内容。

Q13 你觉得你做过的你觉得最值得炫耀的项目?

蚂蚁金服-体验技术部 资深数据可视化研发工程师

一面 电话面 全程 1 小时 24 分钟

Q1 描述一下你最近做的可视化的项目

Q2 刚刚说的 java 调用 js 离线生成数据报告?java 调用 js 的 promise 异步返回结果怎么实现的?

使用 java 的 js 引擎 Nashorn,Nashorn 不支持事件队列,是要引进 polyfill,然后 java 调用 js 方法获得 java 的 promise 对象,然后在调用该对象的 then 方法,回调函数为 java 中的某各类的某个方法,然后 while 一个表示是否已执行回调的变量,如果未执行,则让 java 主线程 sleep,如果已经执行,则跳出循环,表示是否已执行回调的变量在传入 promise 的回调函数中设置更改。详情代码见地址[8]

Q3 说说 svg 和 canvas 各自的优缺点?

共同点:都是有效的图形工具,对于数据较小的情况下,都很又高的性能,它们都使用 JavaScript 和 HTML;它们都遵守万维网联合会 (W3C) 标准。

svg 优点:

矢量图,不依赖于像素,无限放大后不会失真。

以 dom 的形式表示,事件绑定由浏览器直接分发到节点上。

svg 缺点:

dom 形式,涉及到动画时候需要更新 dom,性能较低。

canvas 优点:

定制型更强,可以绘制绘制自己想要的东西。

非 dom 结构形式,用 JavaScript 进行绘制,涉及到动画性能较高。

canvas 缺点:

事件分发由 canvas 处理,绘制的内容的事件需要自己做处理。

依赖于像素,无法高效保真,画布较大时候性能较低。

Q4 你刚刚说的 canvas 渲染较大画布的时候性能会较低?为什么?

因为 canvas 依赖于像素,在绘制过程中是一个一个像素去绘制的,当画布足够大,像素点也就会足够多,那么想能就会足够低。

Q6 假设我现在有 5000 个圆,完全绘制出来,点击某一个圆,该圆高亮,另外 4999 个圆设为半透明,分别说说用 svg 和 canvas 怎么实现?

首先,从数据出发,我们的每个圆是一个数据,这个数据有圆的 x、y、radius、isHighlight 如果是 svg,直接渲染节点即可,然后往节点上边绑定点击事件,点击改变所有数据的高亮属性(必须同步执行完成),然后让浏览器进行绘制。如果是 canvas,我们需要自己绑定事件到 canvans 标签上,然后点击的时候判断点击的位置是否在圆内,如果在某个圆内,则更新所有数据的高亮属性,之后在进行一次性绘制。

Q7 刚刚说的 canvas 的点击事件,怎么样实现?假如不是圆,这些图形是正方形、长方形、规则图形、不规则图形呢。

针对于每一个形状,将其抽象成 shape 类,每一个类有自己的方法 isPointInSide 来判断节点是否在图形内,对于不规则图形,当做矩形处理,点击的时候执行该方法判断点击位置是否在图形内。

Q8 那假如我的图形可能有变形、放大、偏移、旋转的需求呢?你的这个 isPointInSide 怎么处理?

这个我答不出来,据面试官提示,好像有相应的 API 处理变形、旋转、放大等等之后的位置映射关系。

Q9 那个这个 canvas 的点击事件,点击的时候怎么样快速的从这 5000 个圆中找到你点击的那个圆(不完全遍历 5000 个节点)?

可以通过预查找的形式,当鼠标划过的时候预先查找到鼠标附近的一些节点,当点击的时候在从这些预先筛选好的节点里查找点击下来的节点,当然这个方法的前提是不能影响 js 主线程的执行,必须是异步的形式。

Q10 那你用过@antv/g6,里面有一个 tree,说说你大学时候接触到的 tree 的数据结构是怎么实现的?

毕业一年多,tree 的结构大概忘记了,我当时是这么回答的:

大学使用的是 C++学的数据结构,是用指针的形式,首先有一个根节点,根节点里有一个指针数组指向它的所有子节点,然后每一个子节点也是,拥有着子节点的指针数组,一层一层往下,直到为叶子节点,指针数组指向为空。

Q11 还记得二叉树吗?描述二叉树的几种遍历方式?

先序遍历:若二叉树非空,访问根结点,遍历左子树,遍历右子树。

中序遍历:若二叉树非空,遍历左子树;访问根结点;遍历右子树。

后序遍历:若二叉树非空,遍历左子树;遍历右子树;访问根结点。

所有遍历是以递归的形似,直到没有子节点。

Q12 说说你记得的所有的排序,他们的原理是什么?

冒泡排序:双层遍历,对比前后两个节点,如果满足条件,位置互换,直到遍历结束。

快速排序:去数组中间的那一个数,然后遍历所有数,小于该数的 push 到一个数组,大于该数的 push 到另外一个数组,然后递归去排序这两个数组,最后将所有结果连接起来。

选择排序:声明一个数组,每次去输入数组里面找数组中的最大值或者最小值,取出来后 push 到声明的数组中,直到输入数组为空。

Q13 说一下你觉得你做过的最复杂的项目?中间遇到的困难,以及你是怎么解决的?

-


面试官:我这边问题差不多问完了,你还有什么问题?

我:很惊讶今天全都是问可视化相关的,没怎么问 js,css,html。

面试官:那我们继续吧

我:。。。


Q14 那给我介绍一下 react 吧(面试官是做可视化开发的,根本不懂 react)

以前我们没有 jquery 的时候,我们大概的流程是从后端通过 ajax 获取到数据然后使用 jquery 生成 dom 结果然后更新到页面当中,但是随着业务发展,我们的项目可能会越来越复杂,我们每次请求到数据,或则数据有更改的时候,我们又需要重新组装一次 dom 结构,然后更新页面,这样我们手动同步 dom 和数据的成本就越来越高,而且频繁的操作 dom,也使我我们页面的性能慢慢的降低。

这个时候 mvvm 出现了,mvvm 的双向数据绑定可以让我们在数据修改的同时同步 dom 的更新,dom 的更新也可以直接同步我们数据的更改,这个特定可以大大降低我们手动去维护 dom 更新的成本,mvvm 为 react 的特性之一,虽然 react 属于单项数据流,需要我们手动实现双向数据绑定。

有了 mvvm 还不够,因为如果每次有数据做了更改,然后我们都全量更新 dom 结构的话,也没办法解决我们频繁操作 dom 结构(降低了页面性能)的问题,为了解决这个问题,react 内部实现了一套虚拟 dom 结构,也就是用 js 实现的一套 dom 结构,他的作用是讲真实 dom 在 js 中做一套缓存,每次有数据更改的时候,react 内部先使用算法,也就是鼎鼎有名的 diff 算法对 dom 结构进行对比,找到那些我们需要新增、更新、删除的 dom 节点,然后一次性对真实 DOM 进行更新,这样就大大降低了操作 dom 的次数。

那么 diff 算法是怎么运作的呢,首先,diff 针对类型不同的节点,会直接判定原来节点需要卸载并且用新的节点来装载卸载的节点的位置;针对于节点类型相同的节点,会对比这个节点的所有属性,如果节点的所有属性相同,那么判定这个节点不需要更新,如果节点属性不相同,那么会判定这个节点需要更新,react 会更新并重渲染这个节点。

react 设计之初是主要负责 UI 层的渲染,虽然每个组件有自己的 state,state 表示组件的状态,当状态需要变化的时候,需要使用 setState 更新我们的组件,但是,我们想通过一个组件重渲染它的兄弟组件,我们就需要将组件的状态提升到父组件当中,让父组件的状态来控制这两个组件的重渲染,当我们组件的层次越来越深的时候,状态需要一直往下传,无疑加大了我们代码的复杂度,我们需要一个状态管理中心,来帮我们管理我们状态 state。

这个时候,redux 出现了,我们可以将所有的 state 交给 redux 去管理,当我们的某一个 state 有变化的时候,依赖到这个 state 的组件就会进行一次重渲染,这样就解决了我们的我们需要一直把 state 往下传的问题。redux 有 action、reducer 的概念,action 为唯一修改 state 的来源,reducer 为唯一确定 state 如何变化的入口,这使得 redux 的数据流非常规范,同时也暴露出了 redux 代码的复杂,本来那么简单的功能,却需要完成那么多的代码。

后来,社区就出现了另外一套解决方案,也就是 mobx,它推崇代码简约易懂,只需要定义一个可观测的对象,然后哪个组价使用到这个可观测的对象,并且这个对象的数据有更改,那么这个组件就会重渲染,而且 mobx 内部也做好了是否重渲染组件的生命周期 shouldUpdateComponent,不建议开发者进行更改,这使得我们使用 mobx 开发项目的时候可以简单快速的完成很多功能,连 redux 的作者也推荐使用 mobx 进行项目开发。但是,随着项目的不断变大,mobx 也不断暴露出了它的缺点,就是数据流太随意,出了 bug 之后不好追溯数据的流向,这个缺点正好体现出了 redux 的优点所在,所以针对于小项目来说,社区推荐使用 mobx,对大项目推荐使用 redux。

Q15 假如我一个组件有一个状态 count 为 1,然后我在 componentDidMount()里面执行执行了两次 this.setState({count: ++this.state.count}),然后又执行了两次 setTimeout(() => { this.setState({count: ++this.state.count}) }, 0),最后 count 为多少?为什么?

count 为 4,因为第二次执行 setState 的时候,取不到第一次 this.state.count++的结果,react 在一轮生命周期结束后才会更新内部的 state,如果在一轮生命周期内多次使用了 setState,react 内部会有一个字段 isBatchUpdate 标识本次更新为批量更新,然后在最后 render 的时候将所有 setState 的结果提交到 state 中,一次性进行更新,并且把 isBatchUpdate 这个字段设置为 false。

针对于两次 setTimeout,js 引擎会把这两个 setState 丢到事件队列中,等待 js 空闲了去执行,而我们的渲染函数 render 是同步执行的(react16 版本默认没有开启异步渲染),所以等我们 render 执行完全,也就是我们的 state 被同步完后,在取事件队列里面的 setState 进行执行,setTimeout 的第二个 setState 也是一样的,所以最后结果是 4。

备注:这个 count 的答案似乎有疑问,可以实践

最后

这几轮面试的面试官都非常和蔼好交流,百度的五轮面试不知道过了没有,只记得五面的面试官说,你稍等一下,我去问一下其他人对你还有什么其他要求,然后过了一会儿 HR 就喊我先回去了,叫我等 HR 面的消息,如果没通过,也不会在联系我了,已经过了四天了,但愿后面有消息吧。然后有赞、蚂蚁金服的两个一面都过了,因为每次面完试面试官问我还有什么问题吗?我都会询问一下本次面试面试官对我的评论是啥。

后续传送门 -> 记一次前端面试的全过程[10]


END

参考资料

[1]

原文地址: https://github.com/yacan8/blog/blob/master/posts/%E4%B8%80%E5%B9%B4%E5%8D%8A%E7%BB%8F%E9%AA%8C%EF%BC%8C%E7%99%BE%E5%BA%A6%E3%80%81%E6%9C%89%E8%B5%9E%E3%80%81%E9%98%BF%E9%87%8C%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93.md

[2]

详情见: https://reactjs.org/docs/refs-and-the-dom.html#caveats

[3]

数组全排列: https://blog.csdn.net/k346k346/article/details/51154786

[4]

webpack插件归纳总结: https://segmentfault.com/a/1190000016816813

[5]

见地址: https://www.jianshu.com/p/f54d265f7aa4

[6]

promise实现原理: https://segmentfault.com/a/1190000013396601

[7]

地址: https://blog.csdn.net/qzcsu/article/details/72861891

[8]

见地址: https://segmentfault.com/a/1190000012916021

[9]

jsfiddle.net/yacan8/5gsp…: https://jsfiddle.net/yacan8/5gspLrda/13/

[10]

记一次前端面试的全过程: https://juejin.im/post/5bf8fe2ee51d452d705fee3d

最后

  • 欢迎加我微信(winty230),拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个有专业的技术人...

在看点这里

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值