座谈前端机能 打破 React 运用瓶颈

《座谈前端机能 打破 React 运用瓶颈》

机能一直以来是前端开辟中异常重要的话题。跟着前端能做的事变越来越多,浏览器才被无穷放大和运用:从 web 游戏到庞杂单页面运用,从 NodeJS 效劳到 web VR/AR、数据可视化,前端工程师总是在打破极限。随之而来的机能题目有的被水到渠成,有的成为难以逾越的盾墙。

那末,当我们在议论机能时,到底在说什么?基于 React 框架开辟的运用,在机能上又有哪些特性?

这篇文章我们从浏览器和 JavaScript 引擎角度来理会前端机能,同时立异 React,充分运用浏览器才打破范围。

在文章最先之前,我想先向人人引见一本书。

从客岁起,我和着名手艺大佬颜海镜最先了合著之旅,本年我们配合打磨的书本《React 状况治理与同构实战》终究正式出书了!这本书以 React 手艺栈为中心,在引见 React 用法的基础上,从源码层面剖析了 Redux 头脑,同时偏重引见了效劳端衬着和同构运用的架构形式。书中包括许多项目实例,不仅为用户打开了 React 手艺栈的大门,更能提拔读者对前沿范畴的团体认知。

假如列位对图书内容或接下来的内容感兴致,还望多多支撑!文末有概况,不要走开!

机能题目的阿喀琉斯之踵

事实上,机能题目多种多样:瓶颈能够涌现在网络传输历程,形成前端数据显现耽误;也多是 hybrid 运用中,webview 容器带来限定。但是在剖析机能题目时,常常逃不开一个观点——JavaScript 单线程

浏览器剖析衬着 DOM Tree 和 CSS Tree,剖析实行 JavaScript,险些一切的操纵都是在主线程中实行。因为 JavaScript 能够操纵 DOM,影响衬着,所以 JavaScript 引擎线程和 UI 线程是互斥的。换句话说,JavaScript 代码实行时会壅塞页面的衬着。

经由过程下面的图示来举行相识:

《座谈前端机能 打破 React 运用瓶颈》

图中的几个症结角色:

Call Stack:挪用栈,即 JavaScript 代码实行的处所,Chrome 和 NodeJS 中对应 V8 引擎。当它实行完当前一切使命时,栈为空,守候吸收 Event Loop 中 next Tick 的使命。

Browser APIs:这是衔接 JavaScript 代码和浏览器内部的桥梁,使得 JavaScript 代码能够经由过程 Browser APIs 操纵 DOM,挪用 setTimeout,AJAX 等。

Event queue: 每次经由过程 AJAX 或许 setTimeout 增添一个异步回调时,回调函数平常会加入到 Event queue 当中。

Job queue: 这是预留给 promise 且优先级较高的通道,代表着“稍后实行这段代码,但是在 next Event Loop tick 之前实行”。它属于 ES 范例,注重区别对待,这里暂不睁开。

Next Tick: 示意挪用栈 call stack 鄙人一 tick 将要实行的使命。它由一个 Event queue 中的回调,悉数的 job queue,部份或许悉数 render queue 组成。注重 current tick 只会在 Job queue 为空时才会进入 next tick。这就涉及到 task 优先级了,能够人人关于 microtask 和 macrotask 越发熟习,这里不再睁开。

Event Loop: 它会“看管”(轮询)call stack 是不是为空,call stack 为空时将会由 Event Loop 推送 next tick 中的使命到 call stack 中。

在浏览器主线程中,JavaScript 代码在挪用栈 call stack 实行时,能够会挪用浏览器的 API,对 DOM 举行操纵。也能够实行一些异步使命:这些异步使命假如是以回调的体式格局处置惩罚,那末每每会被增添到 Event queue 当中;假如是以 promise 处置惩罚,就会先放到 Job queue 当中。这些异步使命和衬着使命将会鄙人一个时序当中由挪用栈处置惩罚实行。

理解了这些,人人就会邃晓:假如挪用栈 call stack 运转一个很耗时的剧本,比方剖析一个图片,call stack 就会像北京上下班高峰期的环路进口一样,被这个庞杂使命梗塞。主线程其他使命都要列队,进而壅塞 UI 响应。这时刻用户点击、输入、页面动画等都没有了响应。

如许的机能瓶颈,就犹如阿喀琉斯之踵一样,在肯定水平上限定着 JavaScript 的发挥。

两方机能解药

我们平常有两种计划打破上文提到的瓶颈:

  • 将耗时高、本钱高、易壅塞的长使命切片,分红子使命,并异步实行

如许一来,这些子使命会在差别的 call stack tick 周期实行,进而主线程就能够在子使命间隙当中实行 UI 更新操纵。想象罕见的一个场景:假如我们须要衬着一个由十万条数据组成的列表,那末比拟一次性衬着悉数数据,我们能够将数据分段,运用 setTimeout API 去分步处置惩罚,构建衬着列表的事情就被分红了差别的子使命在浏览器中实行。在这些子使命间隙,浏览器得以处置惩罚 UI 更新。

  • 别的一个立异性的做法:运用HTML5 Web worker

Web worker 许可我们将 JavaScript 剧本在差别的浏览器线程中实行。因而,一些耗时的盘算历程我们都能够放在 Web worker 开启的线程当中处置惩罚。下文会有详解。

React 框架机能理会

社区上关于 React 机能的内容每每聚焦在营业层面,主如果运用框架的“最好实践”。这里我们不去议论“运用 shoulComponentUpdate 削减不必要的衬着”、“削减 render 函数中 inline-function”等已“陈词滥调”的话题,本文主要从 React 框架完成层面剖析其机能瓶颈和打破战略。

原生 JavaScript 肯定是最高效的,这个毫无争议。比拟其他框架,React 在 JavaScript 实行层面消费的时刻较多,这是因为:

Virtual DOM 构建 -> 盘算 DOM diff -> 天生 render patch

这一系列庞杂历程所形成的。也就是说,在肯定水平上:React 有名的调理战略 — stack reconcile 是 React 的机能瓶颈。

这并不难理解,因为 DOM 更新只是 JavaScript 挪用浏览器的 APIs,这个历程对一切框架以及原生 JavaScript 来说是一样黑盒实行的,这一部份的机能斲丧是一致且不可避免的。

再来看我们的 React:stack reconcile 历程会深度优先遍历一切的 Virtual DOM 节点,举行 diff。整棵 Virtual DOM 盘算完成以后,将使命出栈开释主线程。所以,浏览器主线程被 React 更新状况使命占有的时刻,用户与浏览器举行任何交互都不能获得反应,只要比及使命完毕,才获得浏览器的响应。

我们来看一个典范的场景,来自文章:React的新引擎—React Fiber是什么?

这个例子会在页面中建立一个输入框,一个按钮,一个 BlockList 组件。BlockList 组件会依据 NUMBER_OF_BLOCK 数值衬着出对应数目的数字显现框,数字显现框显现点击按钮的次数。

《座谈前端机能 打破 React 运用瓶颈》

在这个例子中,我们能够设置 NUMBER_OF_BLOCK 的值为 100000。这时刻点击按钮,触发 setState,页面最先更新。此时点击输入框,输入一些字符串,比方 “hi,react”。能够看到:页面没有任何响应。守候 7s 以后,输入框中倏忽涌现了之前输入的 “hireact”。同时, BlockList 组件也更新了。

不言而喻,如许的用户体验并不好。

浏览器主线程在这 7s 的 performance 以下图所示:

《座谈前端机能 打破 React 运用瓶颈》

黄色部份是 JavaScript 实行时刻,也是 React 占用主线程时刻;
紫色部份是浏览器从新盘算 DOM Tree 的时刻;
绿色部份是浏览器绘制页面的时刻。

这三种使命,统共占用浏览器主线程 7s,此时刻内浏览器没法与用户交互。主如果黄色部份实行时刻较长,占用了 6s,即 React 较长时刻占用主线程,致使主线程没法响运用户输入。这就是一个典范的例子。

React 机能晋级——React Fiber

React 中心团队很早之前就预知机能风险的存在,而且延续探讨可解决的体式格局。基于浏览器对 requestIdleCallback 和 requestAnimationFrame 这两个API 的支撑,React 团队完成新的调理战略 — Fiber reconcile。

更多关于 Fiber 的内容一样引荐文章:React的新引擎—React Fiber是什么?
文章中又在运用 React Fiber 的场景下,反复适才的例子,不会再涌现页面卡顿,交互自但是顺畅。

浏览器主线程的 performance 以下图所示:

《座谈前端机能 打破 React 运用瓶颈》

能够看到:在黄色 JavaScript 实行历程当中,也就是 React 占用浏览器主线程时期,浏览器在也在从新盘算 DOM Tree,而且举行重绘。尽管来看,黄色和紫色等相互交替,同时页面截图显现,用户输入得以及时响应。简朴说,在 React 占用浏览器主线程时期,浏览器也在与用户交互。这显然是“更好的机能”表现。

以上是 React 运用第一种要领:“将耗时高的使命分段”,达到了机能打破。下面我们再来看另一种“民间”做法,运用 Web worker。

React 连系 Web worker

关于 Web worker 的观点此文不再赘述,人人能够接见 MDN 地点举行相识。我们聚焦思考点:假如让 React 接入 Web worker 的话,切入点在那里,该怎样实行?

总所周知,规范的 React 运用由两部份组成:

  • React core:担任绝大部份庞杂的 Virtual DOM 盘算;
  • React-Dom:担任与浏览器实在 DOM 交互来展示内容。

那末答案很简朴,我们尝试在 Web worker 中运转 React Virtual DOM 的相干盘算。行将 React core 部份移入 Web worker 线程中。

确切有人提出了如许的主意,请参考 React 堆栈 第 #3092 号 Issue,这也吸收来了 Dan Abramov 的议论。虽然如许的提案被谢绝,但这并不阻碍我们让 React 连系 worker 做试验。

Talk is cheap, show me the code, and demo:
读者能够接见这里,该网站分别用原生 React 和接入 Web worker 版 React 完成了两个运用,并对照其机能表现。关于代码部份,感兴致的同砚能够私信我。

终究结论:只要当大批的节点发生变化的时,Web worker 提拔衬着机能才会有一些效果。当节点数目异常少的时刻,接入 Web worker 的机能多是负收益。我以为这是因为 worker 线程和主线程之间的通讯本钱而至。

这么看,Web worker 版本的 React 仍有机能提拔空间,我简朴总结以下:

  • 因为 worker 线程和主线程在运用 postMessage 通讯时,机能本钱较大,我们能够采纳 batching 头脑削减通讯的次数。

假如在每次 DOM 须要转变时,都挪用 postMessage 关照主线程,不是迥殊明智。所以能够用 batching 头脑,将 worker 线程中盘算出来的 DOM 待更新内容举行网络,再一致发送。如许一来,batching 的粒度就很有意思了。假如我们走极端,每次 batching 网络的变动都异常多,迟迟不向主线程发送,那末在一次 batching 时就给浏览器真正的衬着历程带来了压力,反而拔苗助长。

  • 运用 postMessage 通报音讯时,采纳 transferable objects 举行数据负载
  • 关于 worker 版 syntheticEvent

原生 React 有一套事宜体系,它在最顶层监听一切的浏览器事宜,以后将它们转化为合成事宜(syntheticEvent),通报给我们在 Virtual DOM 上定义的事宜监听者。

关于我们的 Web worker,因为 worker 线程不能直接操纵 DOM,也就不能监听浏览器事宜。因而一切事宜一样都在主线程中处置惩罚,转化为假造事宜再通报给 worker 线程举行宣布,也就意味着一切关于建立假造事宜的操纵照样都在主线程中举行,一个能够改良的计划能够斟酌直接将原始事宜通报给 worker,由 worker 来天生模仿事宜并冒泡通报。

关于 React 连系 worker 另有许多值得深挖的内容,比方:事宜处置惩罚方面 preventDefault 和 stopPropogation 的同步性保证(worker 线程和主线程通讯是异步的);运用 multiple worker(一个以上 worker)举行探讨等。假如读者有兴致,我会特地写篇文章引见。

Redux 和 Web worker

既然 React 能够接入 Web worker,那末 Redux 固然也能自创如许的头脑,将 Redux 中 reducer 庞杂的纯盘算历程放在 worker 线程里,是不是是一个很好的思绪?

我运用 “N-皇后题目” 模仿大型盘算,除了这个极为耗时的算法,页面中还运转这么几个模块,来完成频仍更新 DOM 的衬着逻辑:

  • 一个及时每 16 毫秒,显现计数(每秒增添 1)的 blinker 模块;
  • 一个定时每 500 毫秒,更新背景色彩的 counter 模块;
  • 一个永远往复运动的 slider 模块;
  • 一个每 16 毫秒翻转 5 度的 spinner 模块

如图:

《座谈前端机能 打破 React 运用瓶颈》

这些模块都定时频仍地更新 DOM 款式,举行衬着。一般情况下,在 JavaScript 主线程举行 N-皇后盘算时,这些衬着历程都将被卡顿。

假如将 N-皇后盘算安排到 worker 线程,我们会发明 demo 展示了使人惊奇的机能提拔,完整丝滑毫无卡顿。如上图,左半部份为一般版本,不出不测涌现了页面卡顿,右边是接入 worker 以后的运用。

在完成层面,借助 Redux 库的 enchancer 设想,完成了笼统封装。
一个 store enhancer,实际上就是一个 curry 化的高阶函数,这和 React 中的高阶组件的观点很相似,同时也相似我们越发熟习的中间件。实在参考 Redux 源码,会发明 Redux 源码中 applyMiddleware 要领的实行效果就是一个 store enhancer。

那末为何不挑选中间件,而是运用 enhancer 来完成呢?这个 Redux worker demo 所采纳的大众库设想思绪异常风趣,关于奇异的 Redux 高阶内容不再睁开,感兴致的读者能够在我新出书的书中找到响应内容。这也就到了广告时刻。。。

《React 状况治理与同构实战》这本书由我和前端着名手艺大佬颜海镜协力打磨,凝结了我们在进修、实践 React 框架历程当中的积聚和心得。除了 React 框架运用引见之外,偏重理会了状况治理以及效劳端衬着同构运用方面的内容。同时吸取了社区大批优异头脑,举行归结比对。

本书遭到百度公司副总裁沈抖、百度资深前端工程师董睿,以及着名 JavaScript 言语专家阮一峰、Node.js 布道者狼叔、Flarum 中文社区创始人 justjavac、新浪挪动前端手艺专家小爝、百度资深前端工程师顾轶灵等前端圈浩瀚专家大咖的联协力荐。

有兴致的读者能够点击这里,相识概况。也能够扫描下面的二维码购置。再次谢谢列位的支撑与勉励!恳请列位批评指正!

《座谈前端机能 打破 React 运用瓶颈》

末了,前端进修永无止境,愿望和每一位手艺爱好者配合进步,人人能够在知乎找到我!

Happy coding!

    原文作者:lucas_580e331d326b4
    原文地址: https://segmentfault.com/a/1190000016008108
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞