数据结构与算法在前端领域的应用 - 换个视角看前端

前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。前端

文章连接:webpack

这是本系列文章的第三篇,这里我将带你重新的视角来看当前的前端应用, 虽然这其中涉及到的道理很简单,可是这部分知识不多被人看到,更不要说推广和应用了。git

这里新的视角指的是咱们从进程和线程的角度来思考咱们前端应用的运行,从而从更高的层次去审视和优化咱们的应用,甚至整个前端生态。程序员

但愿你看完以后从思惟上也好,工做应用中也好可以有所收获。github

关于我

我是一个对技术充满兴趣的程序员, 擅长前端工程化,前端性能优化,前端标准化等。web

作过.net, 搞过 Java,如今是一名前端工程师。面试

除了个人本职工做外,我会在开源社区进行一些输出和分享,GitHub 共计得到 1.5W star。比较受欢迎的项目有leetcode 题解 , 宇宙最强的前端面试指南个人第一本小书算法

浏览器的进程模型

咱们首先来看下浏览器的进程模型,咱们以 chrome 为例。chrome

Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。前端工程化

(图来自 zhuanlan.zhihu.com/p/47407398)

这也是为何 chrome 明明只打开了一个 tab,却出现了 4 个进程的缘由。

这部分不是咱们本节的主要内容,你们了解这么多就够了,接下来咱们看下今天的主角 - 渲染进程。

浏览器的渲染进程

渲染进程几乎负责 Tab 内的全部事情,渲染进程的核心目的在于转换 HTML CSS JS 为用户可交互的 web 页面。

渲染进程由如下四个线程组成:主线程 Main thread , 工做线程 Worker thread,光栅线程 Raster thread 和排版线程 Compositor thread。

咱们的今天的主角是主线程 Main thread 和 工做线程 Worker thread。

主线程 Main thread

主线程负责:

  • 构建 DOM
  • 和网络进程(上文提到的)通讯获取资源
  • 对资源进行解析
  • JS 代码的执行
  • 样式和布局的计算

能够看出主线程很是繁忙,须要作不少事情。 主线程很容易成为应用的性能瓶颈。

固然除了主线程, 咱们的其余进程和线程也可能成为咱们的性能瓶颈,好比网络进程,解决网络进程瓶颈的方法有不少,可使用浏览器自己缓存,也可使用 ServiceWorker,还能够经过资源自己的优化等。这个不是咱们本篇文章的讨论重点,这里只是让你有一个新的视角而已,所以不赘述。

工做线程 Worker thread

工做线程可以分担主线程的计算压力,进而主线程能够得到更多的空闲时间,从而更快地响应用户行为。

工做线程主要有 Web Woker 和 Service Worker 两种。

Web Worker

如下摘自MDN

Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。 线程能够执行任务而不干扰用户界面。此外,他们可使用 XMLHttpRequest 执行 I/O (尽管 responseXML 和 channel 属性老是为空)。 一旦建立, 一个 worker 能够将消息发送到建立它的 JavaScript 代码,

Service Worker

如下摘自MDN

Service workers 本质上充当 Web 应用程序与浏览器之间的代理服务器, 也能够在网络可用时做为浏览器和网络间的代理。 它们旨在(除其余以外)使得可以建立有效的离线体验, 拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采起适当的动做。 他们还容许访问推送通知和后台同步 API。

从新思考咱们的前端应用

工做线程尤为是Web Worker的出现一部分缘由就是为了分担主线程的压力。

整个过程就像主线程发布命令,而后工做线程执行,执行完毕将执行结果经过消息的形式传递给主线程。

咱们以包工头包工程,而后将工做交给各个单位去作的角度来看的话,大概是这样的:

实际上工做工做进程,尤为是WebWorker已经出现很长时间了。可是不少时候咱们并无充分使用,甚至连使用都没使用。

下面以Web Worker为例, 咱们来深度挖掘一下工做线程的潜力。

前面的文章,咱们谈了不少前端领域的算法,有框架层面的也有应用层面的。

前面提到了React的调和算法,这部分代码耗时其实仍是蛮大的,React16重构了 整个调和算法,可是整体的计算成本仍是没有减小,甚至是增长的。

关于调和算法能够参考个人另一篇文章前端领域的数据结构与算法解读 - fiber

咱们有没有可能将这部份内容抽离出主线程,交给工做进程,就像上面的图展现的那样呢? 我以为能够, 另外我前面系列文章提到的全部东西,均可以放到工做线程中执行。 好比状态机,时光机,自动完成,差别比对算法等等。

若是将这些抽离出咱们主线程的话,咱们的应用大概会是这样的:

这样作主线程只负责UI展现,以及事件分发处理等工做,这样就大大减轻了主线程的负担,咱们就能够更快速地响应用户了。 而后在计算结果完成以后,咱们只须要通知主线程,主线程作出响应便可。 能够看出,在项目复杂到必定程度,这种优化带来的效果是很是大的。

咱们来开一下脑洞, 假如流行的前端框架好比React内置了这种线程分离的功能, 即将调和算法交给WebWorker来处理,会给前端带来怎么样的变化?

假如咱们能够涉及一个算法,智能地根据当前系统的硬件条件和网络状态, 自动判断应该将哪部分交给工做线程,哪部分代码交给主线程,会是怎么样的场景?

这其实就是传说中的启发式算法, 你们有兴趣能够研究一下

挑战

上述描述的场景很是美好,可是一样地也会有一些挑战。

第一个挑战就是操做繁琐,好比webworker只支持单独文件引入,再好比不支持函数序列化,以及反复序列化带来的性能问题, 还有和webworker通讯是异步的等等。

可是这些问题都有很成熟的解决方案,好比对于操做比较繁琐这个问题咱们就能够经过使用一些封装好web worker操做的库。comlink 就是一个很是不错的web worker的封装工具库。

对于不支持单文件引入,咱们其实能够用Blob, createObjectURL的方式模拟, 固然社区中其实也有了成熟的解决方案,若是你使用webpack构建的话,有一个worker-loader能够直接用。

对于函数序列化这个问题,咱们没法传递函数给工做线程,其实上面提到的 Comlink, 就很好地解决了这个问题,即便用Comlink提供的proxy, 你能够将一个代理传递到工做线程。

对于反复序列化带来的性能问题,咱们其实可使用一种叫对象转移(Transferable Objects)的技术,幸运的是这个特性的浏览器兼容性也不错。

对于异步的问题,咱们能够采起必定的取舍。 即咱们 本地每次保存一份最近一份的结果拷贝,咱们只须要每次返回这个拷贝, 而后在webworker计算结果返回的时候更新拷贝便可。

总结

这篇文章的主要目的是让你们以新的视角来思考当前的前端应用,咱们站在进程和线程的角度来看如今的前端应用,或许会有更多的不同的理解和思考。

本文先是讲了浏览器的进程模型,而后讲了浏览器的渲染进程中的 线程模型。 咱们知道了渲染进程主要有四个线程组成, 分别是主线程 Main thread , 工做线程 Worker thread,光栅线程 Raster thread 和排版线程 Compositor thread。

而后详细介绍了主线程和工做线程,并以webworker为例,讲述了如何利用工做线程为咱们的主线程分担负担。为了消化这部分知识,建议你本身动手实践一下。

虽然咱们的愿望很好,可是这其中在应用的过程之中仍是有一些坑的,我这里列觉了一些常见的坑,并给出了解决方案。

我相信工做线程的潜力尚未被充分发挥出来,但愿能够看到前端应用真正的挖掘各个进程和线程潜力的时候吧,这不但须要前端工程师的努力,也须要浏览器的配合支持,甚至须要标准化组织去推送一些东西。

关注我

最近我从新整理了下本身的公众号,而且我还给他换了一个名字《脑洞前端》,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你能够听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。

我会尽可能经过图的形式来阐述一些概念和逻辑,帮助你们快速理解,图解前端是个人目标。

以后个人文章同步到微信公众号 脑洞前端 ,您能够关注获取最新的文章,或者和我进行交流。

gongzhonghao
相关文章
相关标签/搜索