ES 6 函數(shù)尾調(diào)用

尾調(diào)用優(yōu)化
什么是尾調(diào)用熊咽?

尾調(diào)用(Tail Call)是函數(shù)式編程的一個(gè)重要概念,本身非常簡(jiǎn)單齐邦,一句話就能說清楚析恋,就是指某個(gè)函數(shù)的最后一步是調(diào)用另一個(gè)函數(shù)。

function f(x){
  return g(x);
}

上面代碼中历造,函數(shù)f的最后一步是調(diào)用函數(shù)g,這就叫尾調(diào)用。

以下三種情況到涂,都不屬于尾調(diào)用。

// 情況一
function f(x){
  let y = g(x);
  return y;
}

// 情況二
function f(x){
  return g(x) + 1;
}

// 情況三
function f(x){
  g(x);
}

上面代碼中颁督,情況一是調(diào)用函數(shù)g之后践啄,還有賦值操作,所以不屬于尾調(diào)用沉御,即使語義完全一樣屿讽。情況二也屬于調(diào)用后還有操作,即使寫在一行內(nèi)吠裆。情況三等同于下面的代碼伐谈。

function f(x){
  g(x);
  return undefined;
}

尾調(diào)用不一定出現(xiàn)在函數(shù)尾部,只要是最后一步操作即可试疙。

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

上面代碼中诵棵,函數(shù)mn都屬于尾調(diào)用,因?yàn)樗鼈兌际呛瘮?shù)f的最后一步操作祝旷。
尾調(diào)用之所以與其他調(diào)用不同履澳,就在于它的特殊的調(diào)用位置。

我們知道怀跛,函數(shù)調(diào)用會(huì)在內(nèi)存形成一個(gè)“調(diào)用記錄”距贷,又稱“調(diào)用幀”(call frame),保存調(diào)用位置和內(nèi)部變量等信息吻谋。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B忠蝗,那么在A的調(diào)用幀上方,還會(huì)形成一個(gè)B的調(diào)用幀漓拾。等到B運(yùn)行結(jié)束什湘,將結(jié)果返回到A长赞,B的調(diào)用幀才會(huì)消失。如果函數(shù)B內(nèi)部還調(diào)用函數(shù)C闽撤,那就還有一個(gè)C的調(diào)用幀得哆,以此類推。所有的調(diào)用幀哟旗,就形成一個(gè)“調(diào)用椃肪荩”(call stack)。

尾調(diào)用由于是函數(shù)的最后一步操作闸餐,所以不需要保留外層函數(shù)的調(diào)用幀饱亮,因?yàn)檎{(diào)用位置、內(nèi)部變量等信息都不會(huì)再用到了舍沙,只要直接用內(nèi)層函數(shù)的調(diào)用幀近上,取代外層函數(shù)的調(diào)用幀就可以了。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

上面代碼中拂铡,如果函數(shù)g不是尾調(diào)用壹无,函數(shù)f就需要保存內(nèi)部變量mn的值、g的調(diào)用位置等信息感帅。但由于調(diào)用g之后斗锭,函數(shù)f就結(jié)束了,所以執(zhí)行到最后一步失球,完全可以刪除f(x)的調(diào)用幀岖是,只保留g(3)的調(diào)用幀。

這就叫做“尾調(diào)用優(yōu)化”(Tail call optimization)实苞,即只保留內(nèi)層函數(shù)的調(diào)用幀豺撑。如果所有函數(shù)都是尾調(diào)用,那么完全可以做到每次執(zhí)行時(shí)黔牵,調(diào)用幀只有一項(xiàng)前硫,這將大大節(jié)省內(nèi)存。這就是“尾調(diào)用優(yōu)化”的意義荧止。

注意,只有不再用到外層函數(shù)的內(nèi)部變量阶剑,內(nèi)層函數(shù)的調(diào)用幀才會(huì)取代外層函數(shù)的調(diào)用幀跃巡,否則就無法進(jìn)行“尾調(diào)用優(yōu)化”。

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

上面的函數(shù)不會(huì)進(jìn)行尾調(diào)用優(yōu)化牧愁,因?yàn)閮?nèi)層函數(shù)inner用到了外層函數(shù)addOne的內(nèi)部變量one素邪。

尾遞歸

函數(shù)調(diào)用自身,稱為遞歸猪半。如果尾調(diào)用自身兔朦,就稱為尾遞歸偷线。

遞歸非常耗費(fèi)內(nèi)存,因?yàn)樾枰瑫r(shí)保存成千上百個(gè)調(diào)用幀沽甥,很容易發(fā)生“棧溢出”錯(cuò)誤(stack overflow)声邦。但對(duì)于尾遞歸來說,由于只存在一個(gè)調(diào)用幀摆舟,所以永遠(yuǎn)不會(huì)發(fā)生“棧溢出”錯(cuò)誤亥曹。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代碼是一個(gè)階乘函數(shù),計(jì)算n的階乘恨诱,最多需要保存n個(gè)調(diào)用記錄媳瞪,復(fù)雜度O(n)

如果改寫成尾遞歸照宝,只保留一個(gè)調(diào)用記錄蛇受,復(fù)雜度O(1)

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

還有一個(gè)比較著名的例子厕鹃,就是計(jì)算 Fibonacci 數(shù)列兢仰,也能充分說明尾遞歸優(yōu)化的重要性。

非尾遞歸的 Fibonacci 數(shù)列實(shí)現(xiàn)如下熊响。

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出

尾遞歸優(yōu)化過的 Fibonacci 數(shù)列實(shí)現(xiàn)如下旨别。

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

由此可見,“尾調(diào)用優(yōu)化”對(duì)遞歸操作意義重大汗茄,所以一些函數(shù)式編程語言將其寫入了語言規(guī)格秸弛。ES6 是如此,第一次明確規(guī)定洪碳,所有 ECMAScript 的實(shí)現(xiàn)递览,都必須部署“尾調(diào)用優(yōu)化”。這就是說瞳腌,ES6 中只要使用尾遞歸绞铃,就不會(huì)發(fā)生棧溢出,相對(duì)節(jié)省內(nèi)存嫂侍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末儿捧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挑宠,更是在濱河造成了極大的恐慌菲盾,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件各淀,死亡現(xiàn)場(chǎng)離奇詭異懒鉴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門临谱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璃俗,“玉大人,你說我怎么就攤上這事悉默〕腔恚” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵麦牺,是天一觀的道長(zhǎng)钮蛛。 經(jīng)常有香客問我,道長(zhǎng)剖膳,這世上最難降的妖魔是什么魏颓? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮吱晒,結(jié)果婚禮上甸饱,老公的妹妹穿的比我還像新娘。我一直安慰自己仑濒,他們只是感情好叹话,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著墩瞳,像睡著了一般驼壶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喉酌,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天热凹,我揣著相機(jī)與錄音,去河邊找鬼泪电。 笑死般妙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的相速。 我是一名探鬼主播碟渺,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼突诬!你這毒婦竟也來了苫拍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤旺隙,失蹤者是張志新(化名)和其女友劉穎绒极,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體催束,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年伏社,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抠刺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塔淤。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖速妖,靈堂內(nèi)的尸體忽然破棺而出高蜂,到底是詐尸還是另有隱情,我是刑警寧澤罕容,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布备恤,位于F島的核電站,受9級(jí)特大地震影響锦秒,放射性物質(zhì)發(fā)生泄漏露泊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一旅择、第九天 我趴在偏房一處隱蔽的房頂上張望惭笑。 院中可真熱鬧,春花似錦生真、人聲如沸沉噩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽川蒙。三九已至,卻和暖如春长已,著一層夾襖步出監(jiān)牢的瞬間畜眨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工痰哨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胶果,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓斤斧,卻偏偏與公主長(zhǎng)得像早抠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撬讽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前蕊连,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法游昼。 上面代碼檢查函數(shù)l...
    陳老板_閱讀 449評(píng)論 0 1
  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前甘苍,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法烘豌。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,392評(píng)論 0 1
  • 1.函數(shù)參數(shù)的默認(rèn)值 (1).基本用法 在ES6之前载庭,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法。
    趙然228閱讀 690評(píng)論 0 0
  • 什么是尾調(diào)用囚聚? 尾調(diào)用(Tail Call)是函數(shù)式編程的一個(gè)重要概念靖榕,本身非常簡(jiǎn)單,一句話就能說清楚顽铸,就是指某個(gè)...
    alex夏夜閱讀 1,829評(píng)論 0 3
  • 姓名:徐芳芳 公司:南京凱弘進(jìn)出口貿(mào)易有限公司 349期努力二組【日精進(jìn)打卡第12天】 【知~學(xué)習(xí)】 《六項(xiàng)精進(jìn)》...
    徐芳芳_4548閱讀 179評(píng)論 0 0