一說到前端你們腦子裏只有,佈局、展現數據、修改樣式等等。但是數據是哪裏來的呢?後端給的後端給的。數據的結構呢?後端給啥用啥。前端
這就是前端的一個軟肋。咱們的業務讓咱們並不須要過深刻的瞭解數據結構,數據結構和算法是一個程序員的基礎。不管是前端開發仍是後端開發、仍是AI機器學習大數據,我認爲都須要必定的數據結構和算法知識(除了前端,其他的都是強烈的剛需。。。),前端的夥伴們學會數據結構有什麼好處呢?改變思考方式,深刻了解js執行的一些過程,在代碼中不知不覺考慮代碼層面的性能優化。用處不少,接下來開始吧。程序員
數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關係的數據元素的集合。(來自百度百科)算法
計算機存儲是什麼意思呢?就是咱們常說的存儲結構。express
組織數據的方式與特定關係呢?就是咱們的邏輯結構。redux
一堆按必定的存儲結構和邏輯結構組合起來的數據集合就是數據結構。後端
這是個人一些理解。因此當一種數據結構擺在個人面前我就會考慮,它是以哪一種形式存儲起來,它有什麼特殊的邏輯組合起來。這種方式有哪些好處呢?當我明白這些的時候,這種數據結構我就算基本瞭解了。數組
首先咱們先說一下線性表,線性表是數據的邏輯結構,元素之間是一對一的關係,你個線性表中,任何一個元素的前一個或者後一個都只有一個元素,而不會出現任何一個元素的前一個或者後一個元素對應多個元素的狀況。瀏覽器
線性表分爲通常性的線性表、與受限的線性表。通常性的線性表就比如數組。就是最多見的通常性線性表,而受限的線性表就是棧與隊列。性能優化
棧型結構,最大的特色是什麼?先進後出,先進入的元素要比後進入的元素更晚的離開這個容器,這像什麼一堆摞起來的書籍。你這能拿走書的最上面的。(你要是給書推倒了,那就只能說明你比較睿智)bash
因此咱們要實現一個棧型結構很天然的想到了
容器:數組
方法:數組的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代碼在執行的時候,會生成一個調用棧(裏面裝滿了全部執行中的函數)
在這個調用棧第一個進棧的也就是壓在最下面的就是全局函數,這個全局函數只會在瀏覽器關閉後纔會出棧,瀏覽器關閉了這個堆棧也就隨之消失了。 當瀏覽器執行下去的時候執行一個函數的時候,就會把這個執行中的函數加入到調用棧中,被調用了就要進調用棧(比較好理解吧),同時控制權也會轉交給這個函數,這個函數中若是有別的函數,執行到別的函數時,作着同樣的事情,進棧。當任何一個函數執行結束以後,那麼很差意思,他就要退棧了。離開這個調用棧了。由於調用棧中內容過多,表明着垃圾資源沒有回收,從而致使瀏覽器卡頓,這是不合理的,執行完畢就會退棧。退棧以後,那麼執行權就要交回到包含着它的函數了。
當咱們沒有考慮遞歸的出口的時候, 簡化函數 function fn(){ let a = 1; fn() } fn() // 報錯!!!! Maximum call stack size exceeded // 最大調用堆棧大小超過 複製代碼
當我沒有設定出口時,並無任何一個函數會出棧,在不斷的循環調用後,你的堆棧確定不會是無限的,那麼就只好提醒你堆棧溢出,程序報錯。
所謂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後面的代碼直到退棧。可能這種簡單的模式你們看起來比較清晰,若是有比較複雜的內容,你們記得畫圖不要弄錯了。
爲何閉包使用過多會致使程序卡頓,性能很差???
這個問題很讓人費解,可是扯上調用棧後,我以爲能夠解釋一波(代碼我就不上了,你們能夠去找找閉包得代碼看一看) 閉包得私有變量怎麼產生的? 在一個函數執行後,它執行完畢就必定會退棧。
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等內容~