天天看點

JS調用棧

調用棧 JS執行時會形成調用棧,調用一個函數時,傳回位址、參數、本地變量都會被推入棧中,如果目前正在運作的函數a調用函數b,則函數b相關内容也會被推入棧頂。函數a執行完畢後,與a相關的内容都會被彈出調用棧。由于複雜類型值存放于堆中,是以彈出的隻是指針,他們的值依然在堆中,由GC決定回收。

換而言之,函數調用會在記憶體中形成一個“調用記錄”,又稱“調用幀”,儲存調用位置和内部變量等資訊。如果在函數a的内部調用函數b,那麼在a的調用幀上方,還會形成一個b的調用幀。等到b運作結束,将結果傳回到a,b的調用幀才會消失。如果函數b的内部還調用函數c,那就還有一個c的調用幀,以此類推。所有的調用幀就形成一個“調用棧”。

尾調用 尾調用是指某個函數的最後一步是調用另一個函數。

尾調用優化 看下面的例子:

function g (item){
    return item
}
function f(){
    let m = 1
    let n = 2
    return g(m + n)
}
f()
// 上面的例子實際等同于  g(3)
複制代碼
           

上面的代碼中,如果函數g不是函數f的尾調用,函數f就需要儲存内部變量m和n的值、g的調用位置等資訊。但是由于調用g之後,函數f就結束了,是以執行到最後一步,完全可以删除函數f的調用幀,隻保留g(3)的調用幀。 這就叫“尾調用優化”,即隻保留内層函數的調用幀。如果所有函數都是尾調用,那麼完全可以做到每次執行時,調用幀隻有一項,這樣大大節省記憶體,這就是“尾調用優化”的意義。 注意,隻有不再用到外層函數的内部變量,内層函數的調用幀才會取代外層函數的調用幀,否則無法進行“尾調用優化”。

function addOne(a){
    let one = 1
    function inner(b){
        return b + one
    }
    return inner(a)
}
複制代碼
           

顯而易見,上面的函數不會進行尾調用優化,因為内層函數inner用到了外層函數addOne的内部變量one。

尾遞歸 遞歸就是函數調用自身,如果尾調用的函數是函數自身,就稱為尾遞歸。 遞歸非常消耗記憶體,因為需要同時儲存成千上百個調用幀,很容易發生“棧溢出”,但是對于尾遞歸來說,隻存在一個調用棧,就不會發生“棧溢出”錯誤

轉載于:https://juejin.im/post/5cb67ffaf265da03b11f2e4a