數據結構與算法在前端領域的應用

這是一個我即將作的一個《數據結構與算法在前端領域的應用》主題演講的一個前菜。
但願經過這個分享讓你們認識到其實前端領域也有不少算法的,從而加深前端同窗對算法的認識。
若是你們對數據結構和算法感興趣,歡迎關注個人我的公衆號,或者入羣和我交流,二維碼在文章末尾。前端

關於我

我是一個對技術充滿興趣的程序員, 擅長前端工程化,前端性能優化,前端標準化等。vue

作過.net, 搞過Java,如今是一名前端工程師。node

除了個人本職工做外,我會在開源社區進行一些輸出和分享,GitHub 共計得到1.5W star。比較受歡迎的項目有leetcode題解 , 宇宙最強的前端面試指南
個人第一本小書react

爲何你們以爲算法沒用

在個人職業生涯中,碰到不少非算法崗的研發同窗吐槽「算法在實際業務中沒什麼用」,
甚至在面試官也問過我這個問題。咱們姑且不來判斷這句話正確與否,咱們先來看下爲何你們會有這樣的想法。webpack

我發現不少人喜歡用冰山這個圖來表示這種只看到整體的一小部分的狀況。
我也來借用一下這個創意。git

圖片描述

根據個人經驗,咱們寫的業務代碼一般不到總包體的 5%, 下面是我之前作過的一個實際項目的代碼分佈。程序員

$ du -sh node_modules # 429M
 $ du -sh src # 7.7M

你們能夠拿本身的實際項目看一下,看是否是這樣的。github

其實不難看出業務代碼在整個應用的比例是很小的,軟件工程有一個至理名言,「軟件開發的 90%的工做是由 10%的人完成的」,
這句話很對,那麼剩下的 10 的工做卻由剩下的 90%來完成。web

所以咱們感受算法沒用,是由於咱們沒用深刻剩下的「90%」
不少場景咱們接觸不到,而且沒有思考過,就很容易「井底之蛙」,到頭來就變成「只會用別人造好的輪子組裝代碼」的「前端打字員」。面試

那剩下的 90% 究竟有哪些涉及到算法呢?是否能夠舉例說明呢?
那接下來讓咱們來繼續看一下。

前端中算法的應用場景都有哪些

說實話,這部份內容實在太多啦,爲了讓你們有一個直觀的感覺,我畫了一個圖。

圖中黃色的表明我本身實現過。

16c3958b6c571793?w=660&h=465&f=jpeg&s=42971

這些都是前端開發過程的一些東西, 他們多多少少涉及到了數據結構和算法的知識

下面咱們來簡單分析一下。

VDOM

事實上 VDOM 就是一種數據結構,可是它並非咱們在《數據結構與算法》課程中學習到的一些現成的數據結構。

邏輯上 VDOM 就是用來抽象 DOM 的,底層上 VDOM 廣泛實現是基於 hash table 這種數據結構的。

一個典型的 VDOM 能夠是:

{
  type: 'div',
  props: {
    name: 'lucifer'
  },
  children: [{
    type: 'span',
    props: {},
    children: []
  }]
}

不難看出,VDOM 是一種遞歸的數據結構,所以使用遞歸的方式來處理是很是直觀和容易的。

我上面提到了 VDOM 是 DOM 的抽象(ye, a new level of abstraction)。
根據 VDOM 咱們能夠建立一個對應的真實 DOM。

若是咱們只是作了這一層抽象的話,咱們至關於引入了一種編程模式,即從
面向 DOM 編程,切換到面向 VDOM 編程,而如今 VDOM 又是由數據驅動的,所以
咱們的編程模式切換到了「數據驅動」。

事實上,VDOM 部分還有一個 VDOM diff 算法,相信你們都據說過。關於
DOM diff 的算法,以及它是如何取捨和創新的,我以前在一個地方回答過,這裏給一個連接地址:
https://juejin.im/post/5d3d8c...

Hooks

Hooks 是 React16 添加的一個新功能, 它要解決的問題是狀態邏輯複用。

Hooks 邏輯上解決了純函數沒法持久化狀態的「問題」,從而拓寬了純函數組件的
適用範圍。

底層上 Hooks 使用數據來實現狀態的對應關係,關於這部分能夠參考個人
[[第一期]實現一個簡化版的 React Hook - useState](https://github.com/azl3979858...

Fiber

Fiber 也是 React16 添加的一個新功能。

事實上 Fiber 相似 VDOM,也是一個數據結構,並且一樣也是一個遞歸的數據結構。

爲了解決 React 以前一次全量更新的"問題", React 引入了 fiber 這種數據結構,
並重寫了整個調和算法,而且劃分了多個階段。 關於這部份內容,我只推薦一篇文章,
Inside Fiber: in-depth overview of the new reconciliation algorithm in React

其實以前個人 從零開始實現 React 系列教程 也欠了 fiber 😄, 等我開心的時候補充上去哈。

Git

我以前寫過一個 Git 終端(代碼被我 rm -rf 啦)。 這過程仍是用到了不少數據結構和算法的,
我也學到了不少東西, 甚至 React16 新的調和算法也有 Git 思想。

很直觀的,Git 在推送本地倉庫到遠程的時候會進行壓縮,其實這裏就用到了最小編輯距離算法。
Leetcode 有一個題目72. Edit Distance,
官方難度hard, Git 的算法要是這個算法的複雜版本。

另外 Git 其實不少存儲文件,數據的時候也用到了特殊的數據結構,我在這裏
進行了詳細的描述,感興趣的能夠去看看。

Webpack

Webpack 是衆所周知的一個前端構建工具,咱們能夠用它作不少事情。
至今在前端構建領域仍是龍頭老大 🐲 的位置。

Webpack 中最核心的 tapable 是什麼,是如何配合插件系統的? webpack 是如何對資源進行抽象的,
webpack 是如何對依賴進行處理的?更復雜一點 Tree Shaking 如何作,分包怎麼作,
加速打包怎麼作。

其實 webpack 的執行過程是基於事件驅動的,tapable 提供了一系列鉤子,
讓 plugin 深刻到這些過程之中去。聽起來有點像事件總線,其實其中的設計思想和算法
細節要複雜和精妙不少。

關於這部分細節,我在個人從零實現一個 Webpack

以後會加入更多特性,好比 tapable

AST

AST(抽象語法樹)是前端 編譯(嚴格意義上是轉義)的理論基礎,
你若是想深刻前端編譯,就必定不能不會這個知識點。

和 AST 類似的,其實還有 CST,prettier 等格式化工具會用到, 有興趣能夠搜一下。

這個網站 可讓你對 AST 有一個直觀的認識。

AST 厲害就厲害在它自己不涉及到任何語法,所以你只要編寫相應的轉義規則,就能夠將任何語法轉義到任何語法。
這就是babelPostCSS, prettiertypescript 等的原理,
除此以外,還有不少應用場景,好比編輯器。

以前本身寫過一個小型的生成 AST 的程序,源代碼忘記放哪了。😅

Browser History

像瀏覽器中的歷史頁面,移動端 webview 的 view stack, 都用到了這種數據結構。

剩下的我就不一一說了。其實都是有不少數據結構和算法的影子的。

OK,說了那麼多。 這些都是「大牛」們作的事情,好像和我平常開發不要緊啊。
我只要用他們作好的東西,調用一下,同樣能夠完成個人平常工做啊。
讓咱們帶着這個問題繼續往下看。

算法在平常開發中的應用都有哪些

大神: 「你能夠先這樣,再這樣,而後就會抽象爲純算法問題了。」

我: 「哇,好厲害。」

其實就是你沒有掌握,或者「再思考」,以致於不能融匯貫通。

好比你能夠用 vue 組件寫一個遞歸,實現一些遞歸的功能,也是能夠的,
可是大多數人都想不到。

接下來,我會舉幾個例子來講明「算法在平常開發中的應用」。
注意,如下全部的例子均來自個人實際業務需求。

第一個例子 - 撤銷與重作

業務描述

某一天,可(gai)愛(si)的產品提了一個需求,」咱們的系統須要支持用戶撤銷和重作最近十次的操做。「

16c39594a4ee8877?w=247&h=239&f=jpeg&s=19960

讓咱們來回憶一下純函數。

16c395973b454955?w=635&h=387&f=jpeg&s=35339

純函數有一個特性是給定輸入,輸出老是同樣的。

關於函數式編程能夠參考我以前的 幾篇文章
一個分享

咱們對問題進行一些簡化,假設咱們的應用是純粹的數據驅動,也就是說知足的特性。

16c3959ad8bbbcf3?w=570&h=324&f=jpeg&s=26760

咱們繼續引入第二個知識點 - reducer.

reducer 是一個純函數,函數簽名爲(store1, action1) => store2
即給定 state 和 action,必定會返回肯定的新的 state。

本質上 reducer 是 reduce 的空間版本。

假設咱們的應用初始 state 爲 state1, 咱們按照時間前後順序分別發送了三個 action,
action1, action2, action3。

咱們用一個圖來表示就是這樣的:

16c3959f6049f012?w=810&h=239&f=jpeg&s=32948

運用簡單的數據知識,咱們不難推導出以下關係:

16c395a1eabc6f54?w=764&h=239&f=jpeg&s=31242

若是對這部分知識點還比較迷茫,能夠看下我以前的一篇文章, 從零實現一個 Redux

解決方案

基礎知識鋪墊完了,咱們來看一下怎麼解決這個問題。

第一種方案,咱們能夠將每次的store,即store1, store2, store3都存起來。
好比我想回退到第二步,咱們只須要將store2取出來,覆蓋當前store,而後從新渲染便可。
這種方案很直觀,能夠知足咱們的業務需求。
可是缺點也很明顯,store在這裏被存儲了不少。 每次發送一個action都會有一個新的store被存起來。
當咱們應用比較大的時候,或者用戶觸發了不少action的時候,會佔據大量內存。
實際場景中性能上咱們很難接受。

16c395a6000f2212?w=575&h=233&f=jpeg&s=21480

第二種方案,有了上面的鋪墊,咱們發現, 事實上咱們不必存儲全部的store。
由於store能夠被計算出來。所以咱們只要存儲action便可。
好比咱們要回退到第二步,咱們拿出來store1,而後和action運算一次,獲得store2,
而後將store2覆蓋到當前的store便可。

16c395a894d842d5?w=584&h=255&f=jpeg&s=24194

這種作法,只須要存儲一個store1, 以及若干個action。 action相對於store來講要小不少。
這是這種作法相比與上一種的優點。同時因爲每次都須要從頭(store1)開始計算,
所以是一種典型的「時間換空間」的優化手段。

實際上這種作法,咱們能夠進行小小的優化。好比咱們設置多個snapshot,
而後咱們就沒必要每次從頭開始計算,而是算出最近的一個snapshot,
而後計算便可。 無疑這種作法能夠減小不少計算量,可是會增長空間佔用。
這是典型的「空間換時間」, 若是根據實際業務進行取捨是關鍵。

第三種方案,咱們能夠用樹來表示咱們的store。每次修改store,咱們不是將整個store
銷燬而後建立一個新的,而是重用能夠重用的部分。

16c395acfa40fafd?w=745&h=379&f=jpeg&s=51537

如圖我要修改 store.user.age。咱們只須要將root和user的引用進行修改,同時替換age節點便可。

若是你們對immutable研究比較深的話應該能發現,這其實就是immutable的原理

第二個例子 - 巨型Mapper的優化

業務描述

因爲業務須要,咱們須要在前端緩存一些HTTP請求。
咱們設計了以下的數據結構,其中key表示資源的URL,
value會上次服務端的返回值。

如今咱們的項目中已經有上千個接口,當接口多起來以後,緩存佔用會比較大,咱們如何對此進行優化?

注: 咱們的key中的前綴是有規律的,即有不少重複的數據在。 返回值也有多是有不少重複的。

16c395afdf2c65ce?w=341&h=295&f=jpeg&s=34399

這是一個典型的數據壓縮算法。數據壓縮算法有不少,我這裏就不介紹了,你們能夠自行了解一下。

對數據壓縮算法感興趣的,能夠看下我以前寫的遊程編碼和哈夫曼編碼

第三個例子 - 實現自動聯想功能

業務描述

如今不少輸入框都帶了自動聯想的功能, 不少組件庫也實現了自動填充組件。

如今須要你完成這個功能,你會怎麼作?

16c395b41773bab0?w=356&h=108&f=jpeg&s=11605

咱們能夠用前綴樹,很高效的完成這個工做。

16c39566522a8824?w=827&h=602&f=png&s=71784

對這部分原理感興趣的能夠看下個人這個題解

第四個例子 - 類似度檢測

業務描述

因爲業務須要,咱們須要對字符串進行類似度檢測。
對於類似度超過必定閥值的數據,咱們認爲它是同一個數據。

16c395b7de1b8de8?w=677&h=280&f=jpeg&s=68716

關於類似度檢測,咱們其實能夠藉助「最小編輯距離」算法。
對於兩個字符串a和b,若是a和b的編輯距離越小,咱們認爲越類似,
反之越不類似。 特殊狀況,若是編輯距離爲0表示是相同的字符串,
類似度爲100%。 咱們能夠加入本身的計算因子,將類似度
離散在0 - 100%之間。

這部分的內容,我在介紹Git的時候介紹過了,這裏再也不重複。

其實咱們能夠進一步擴展一下,好比對於一些無心義的詞不計入統計範圍,咱們能夠怎麼作?

算法很差會怎麼樣

這恐怕是不少人最關心的問題。

我雖然知道了算法有用,可是我不會怎麼辦?會有什麼樣的影響呢?

這就回到了咱們開頭的問題,「爲何不少人以爲算法沒用」。
事實上,咱們平常開發中真正用到算法的場景不多,大部分都被別人封裝好了。
即便真正須要用到一些算法,咱們也能夠經過一些「低劣」的手段完成,在不少對性能和質量要求
不高的業務場景都是沒有問題的。 這就是爲何「前端同窗更加以爲算法沒用」的緣由之一。

那既然這麼說,是否是真的算法就沒用呢? 或者說算法很差也不會怎麼樣了麼?
固然不是, 若是算法很差,會很難創新和突破。 想一想現在前端框架,工具的演進,哪個不是創建在
無數的算法之上。 將視角聚焦到咱們當下的業務上,若是算法很差,咱們也一樣很難讓業務不斷精進,
不斷賦能業務。

React框架就是一個很是典型的例子,它的出現改變了傳統的編程模式。
Redux的做者,React團隊現任領導者 dan 最近發表了一篇我的博客 Algebraic Effects for the Rest of Us
這裏面也有不少算法相關的東西,你們有興趣的能夠讀讀看。

另外我最近在作的一個 stack-visualizer,一個用於跟蹤瀏覽器堆棧信息,以便更好地調試地工具, 這個也是和算法有關係的。

關注我

最近我從新整理了下本身的公衆號,而且我還給他換了一個名字《腦洞前端》,它是一個幫助你打開大前端新世界大門的鑰匙🔑,在這裏你能夠聽到新奇的觀點,看到一些技術嘗新,還會收到系統性總結和思考。

我會盡可能經過圖的形式來闡述一些概念和邏輯,幫助你們快速理解,圖解前端是個人目標。

以後個人文章同步到微信公衆號 腦洞前端 ,您能夠關注獲取最新的文章,或者和我進行交流。

交流羣

如今仍是初級階段,須要你們的意見和反饋,爲了減小溝通成本,我組建了交流羣。你們能夠掃碼進入

QQ 羣

qq-group-chat

微信羣

(因爲微信的限制,100我的以上只能邀請加入, 你能夠添加個人機器人回覆「大前端」拉你進羣)

相關文章
相關標籤/搜索