數據結構與算法在前端領域的應用 - 換個視角看前端

前一段時間我分享了幾篇關於《數據結構與算法在前端領域的應用》的文章。前端

文章連接: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
相關文章
相關標籤/搜索