平時在前端開發中,好像也沒啥用到遞歸的地方。不過這並不表明遞歸不重要,若是你看過一些框架的源碼,就會常常見到它的影子:好比渲染虛擬DOM的render函數,webpack中require依賴分析,Koa2洋蔥式的中間件模型,其實都運用到了遞歸算法。javascript
博客原文前端
那麼遞歸究竟是啥?先上兩張圖:java
圖1:webpack
圖2:web
遞歸,就是在運行的過程當中調用本身
咱們來看個最簡單的階乘函數:算法
5! = 5 * 4 * 3 * 2 * 1
function factorial(num) { if (num === 1) { // 基線條件 return 1; } // 遞歸條件 return num * factorial(num-1); } factorial(5);
一個常規的遞歸函數都有兩部分:數據結構
if (num === 1)
):保證函數再也不調用本身,避免無限循環num * factorial(num-1)
):保證函數可以調用本身棧是一種先進後出的數據結構,它只有兩種操做,出棧和入棧框架
const nekopara = ['chocolat', 'Coconut']; nekopara.push('vanilla'); // 入棧 nekopara.pop(); // 出棧
代碼在運行過程當中,會有一個叫作調用棧(call stack)的概念。函數
function greet(name) { console.log(`hello, ${name}!`) greet2(name); console.log(`getting ready to say bye`); bye(); } function greet2(name) { console.log(`how are you, ${name}?`); } function bye() { console.log(`bye`); } greet('deepred');
調用greet('deepred')
時,計算機會首先給該函數分配一塊內存,並將變量名name
設置爲deepred
性能
每當調用函數時,都會分配一個內存塊並將涉及到的變量值存儲到內存中。
打印hello, deepred
後,調用了greet2('deepred')
。一樣,計算機再次分配了一塊內存,而且該內存塊位於第一個內存塊上面。
調用棧的最上面表示當前運行的函數,如圖所示,如今正在運行的是greet2函數,打印輸出how are you, deepred?
後,函數greet2執行完畢,棧頂的內存塊被彈出。
如今棧頂的內存塊又變回greet,這意味着咱們從greet2的函數中跳出,再次返回到了greet。
咱們在greet中調用了greet2時,greet只執行了一部分。
特別注意:調用另一個函數時,當前函數暫停而且處於未完成狀態,暫停函數的全部變量的值仍然在內存中。
執行完greet2後,咱們回到了greet,並從離開的地方開始接着往下執行:首先打印getting ready to say bye
,而後調用bye函數。
在棧頂添加了bye函數的內存塊後,開始執行bye函數,打印bye
,而後函數返回,內存塊被彈出。
咱們又再次回到了greet中,此次沒有其餘要運行的代碼了,因而從greet函數中返回,內存塊被彈出,調用棧最後爲空。
完整的一次調用流程:
遞歸一樣使用調用棧
咱們來分析下階乘fact(3)
的調用棧
function fact(num) { if (num === 1) { return 1; } return num * fact(num-1); } fact(3);
直接看圖:
遞歸會致使程序的性能變低
若是遞歸嵌套很深,那麼調用棧會很長,這將佔用大量內存,可能會致使棧溢出