前端也要会的数据结构 (不定期更新篇)
前端的軟肋
一說到前端大家腦子里只有,布局、展示數據、修改樣式等等。可是數據是哪里來的呢?后端給的后端給的。數據的結構呢?后端給啥用啥。
這就是前端的一個軟肋。我們的業務讓我們并不需要過深入的了解數據結構,數據結構和算法是一個程序員的基礎。無論是前端開發還是后端開發、還是AI機器學習大數據,我認為都需要一定的數據結構和算法知識(除了前端,其余的都是強烈的剛需。。。),前端的伙伴們學會數據結構有什么好處呢?改變思考方式,深入了解js執行的一些過程,在代碼中不知不覺考慮代碼層面的性能優化。用處很多,接下來開始吧。
什么是數據結構?
數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關系的數據元素的集合。(來自百度百科)
計算機存儲是什么意思呢?就是我們常說的存儲結構。
組織數據的方式與特定關系呢?就是我們的邏輯結構。
一堆按一定的存儲結構和邏輯結構組合起來的數據集合就是數據結構。
這是我的一些理解。所以當一種數據結構擺在我的面前我就會考慮,它是以哪種形式存儲起來,它有什么特殊的邏輯組合起來。這種方式有哪些好處呢?當我明白這些的時候,這種數據結構我就算基本了解了。
首先我們先說一下線性表,線性表是數據的邏輯結構,元素之間是一對一的關系,你個線性表中,任何一個元素的前一個或者后一個都只有一個元素,而不會出現任何一個元素的前一個或者后一個元素對應多個元素的情況。
線性表分為一般性的線性表、與受限的線性表。一般性的線性表就好比數組。就是最常見的一般性線性表,而受限的線性表就是棧與隊列。
好了好了 我們開始正題了
你好 神奇的棧結構
棧型結構,最大的特點是什么?先進后出,先進入的元素要比后進入的元素更晚的離開這個容器,這像什么一堆摞起來的書籍。你這能拿走書的最上面的。(你要是給書推倒了,那就只能說明你比較睿智)
所以我們要實現一個棧型結構很自然的想到了
容器:數組
方法:數組的push與pop方法 下面開始實現一個棧的構造函數
function Stack(){// 用let創建一個私有容器,無法用this選擇到dataStore;let dataStore = [];// 模擬進棧的方法 this.push = function(element){dataStore.push(element);};// 模擬出棧的方法,返回值是出棧的元素。this.pop = function(){return dataStore.pop();};// 返回棧頂元素this.peek = function(){return dataStore[dataStore.length-1]; };// 是否為空棧this.isEmpty = function(){return dataStore.length === 0 };// 獲取棧結構的長度。this.size = function(){return dataStore.length;};// 清除棧結構內的所有元素。this.clear = function(){dataStore = [];} }好了伙伴們 棧的構造函數我們已經寫好了。
// 一個單獨的棧生成了。 let stack = new Stack(); stack.push(1); stack.push(2); stack.push(5); stack.peek(); // return 5 stack.size(); // return 3 stack.clear(); stack.peek(); // undefined一個基本的棧型結構就實現了。 真的很是簡單。但是這個東西在js中有什么用處呢?
在js中的用處
用處很大,首先我們都知道遞歸如果沒有設置好邊界值,就會報堆棧溢出。 什么意思??? 我們的js代碼在執行的時候,會生成一個調用棧(里面裝滿了所有執行中的函數)
在這個調用棧第一個進棧的也就是壓在最下面的就是全局函數,這個全局函數只會在瀏覽器關閉后才會出棧,瀏覽器關閉了這個堆棧也就隨之消失了。 當瀏覽器執行下去的時候執行一個函數的時候,就會把這個執行中的函數加入到調用棧中,被調用了就要進調用棧(比較好理解吧),同時控制權也會轉交給這個函數,這個函數中如果有別的函數,執行到別的函數時,做著一樣的事情,進棧。當任何一個函數執行結束之后,那么不好意思,他就要退棧了。離開這個調用棧了。因為調用棧中內容過多,代表著垃圾資源沒有回收,從而導致瀏覽器卡頓,這是不合理的,執行完畢就會退棧。退棧之后,那么執行權就要交回到包含著它的函數了。
堆棧的三種場景
1:遞歸是一種怎樣的情況呢?
當我們沒有考慮遞歸的出口的時候, 簡化函數 function fn(){let a = 1; fn() } fn() // 報錯!!!! Maximum call stack size exceeded// 最大調用堆棧大小超過當我沒有設定出口時,并沒有任何一個函數會出棧,在不斷的循環調用后,你的堆棧肯定不會是無限的,那么就只好提醒你堆棧溢出,程序報錯。
2: 你知道redux的洋蔥模型圖嗎?
所謂redux的洋蔥模型(其實redux我沒用過,但是在公司分享會上聽過一段對于這個洋蔥模型圖的分享),大家也可以理解一下express框架的寫接口時的next函數。
在我們洋蔥在最外層執行完畢后就會進入里面,到最內部后再循壞退出來。 function fn1(){console.log('fn1 first');fn2()console.log('fn1 last'); } function fn2(){console.log('fn2 first');fn3()console.log('fn2 last'); } function fn3(){console.log('fn3 first');console.log('fn3 last'); } fn1() 打印結果中我們可以看出,fn1執行的時候在,遇到fn2執行進棧后,將控制權轉交給fn2,fn2執行遇到fn3執行進棧,將控制權交給fn3,fn3執行完畢后退棧,控制權還給fn2,那么fn2后面的代碼會繼續執行,fn2的代碼執行完畢,退棧。繼續執行fn1后面的代碼直到退棧。可能這種簡單的模式大家看起來比較清晰,如果有比較復雜的內容,大家記得畫圖不要弄錯了。3: 不說你就會忘的閉包
為什么閉包使用過多會導致程序卡頓,性能不好???
這個問題很讓人費解,但是扯上調用棧后,我覺得可以解釋一波(代碼我就不上了,大家可以去找找閉包得代碼看一看) 閉包得私有變量怎么產生的? 在一個函數執行后,它執行完畢就一定會退棧。
function A(){var count = 0;function B(){count ;console.log(count);}return B; } var C = A(); C();// 1 C();// 2 C();// 3不好意思我食言了,方便大家理解還是上一波代碼吧。
在A函數執行得時候,我們發現執行過程中,遇到B函數,好了它開始調用進棧執行,執行完畢后,控制權回歸A函數,然后把B函數return出去了。B函數中保持對count變量的引用,你就把它return出去了????好吧你愿意你就這么干。
B函數被推出去到外面的世界(外面的函數體內);將B賦值給C,好了C需要count變量的支持,count就不能離開內存(也就是不能被垃圾回收);拿咋辦? A函數執行完了我也該離開了(函數執行完畢后,函數內部的變量會被回收掉)。不好意思,外面執行著的函數還有對你的引用,那不好意思你別退棧了,并不允許離開調用棧,因為要保留count變量的環境。
好吧每一個這種情況就會有幾個函數無法退棧,調用棧里面的內容越堆越多。就會越加卡頓。一輛公交車,咱們的乘客到站就下車了,但是總有幾個乘客死活不下車,車上人越來越多,車也越來越沉,好吧跑起來就越來越慢了。
是時候結個尾了
在不理解棧的時候,我很難去想以上幾種情況,數據結構真的在改變我的思路,一切的知識點都在這些基礎中有著驗證,夯實基礎,我認為數據結構也不應該是前端的加分項,而是比會項。最后的最后,我用我聽過的一句很經典的話來結尾好了
我們寫的代碼不是為了更好的和人去溝通,而是去更好的和機器溝通
最后打個廣告(我們一起維護的學習公眾號)
公眾號主要面向的是初級/應屆生。內容包含我們從應屆生轉換為職場開發所踩過的坑,以及我們每周的學習計劃和學習總結。 內容會涉及計算機網絡、算法等基礎;也會涉及前端,后臺,Android等內容~
我們基友團其他朋友的文章:
Android基友
Java基友
總結
以上是生活随笔為你收集整理的前端也要会的数据结构 (不定期更新篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一套比较完整的前端技术选型,需要规整哪些
- 下一篇: 在vue中使用SockJS实现webSo