免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
尾調(diào)用優(yōu)化


一、什么是尾調(diào)用?

尾調(diào)用的概念非常簡單,一句話就能說清楚,就是指某個函數(shù)的最后一步是調(diào)用另一個函數(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;}

上面代碼中,情況一是調(diào)用函數(shù)g之后,還有別的操作,所以不屬于尾調(diào)用,即使語義完全一樣。情況二也屬于調(diào)用后還有操作,即使寫在一行內(nèi)。

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

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

上面代碼中,函數(shù)m和n都屬于尾調(diào)用,因為它們都是函數(shù)f的最后一步操作。

二、尾調(diào)用優(yōu)化

尾調(diào)用之所以與其他調(diào)用不同,就在于它的特殊的調(diào)用位置。

我們知道,函數(shù)調(diào)用會在內(nèi)存形成一個"調(diào)用記錄",又稱"調(diào)用幀"(call frame),保存調(diào)用位置和內(nèi)部變量等信息。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B,那么在A的調(diào)用記錄上方,還會形成一個B的調(diào)用記錄。等到B運行結(jié)束,將結(jié)果返回到A,B的調(diào)用記錄才會消失。如果函數(shù)B內(nèi)部還調(diào)用函數(shù)C,那就還有一個C的調(diào)用記錄棧,以此類推。所有的調(diào)用記錄,就形成一個"調(diào)用棧"(call stack)。

尾調(diào)用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調(diào)用記錄,因為調(diào)用位置、內(nèi)部變量等信息都不會再用到了,只要直接用內(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)部變量m和n的值、g的調(diào)用位置等信息。但由于調(diào)用g之后,函數(shù)f就結(jié)束了,所以執(zhí)行到最后一步,完全可以刪除 f() 的調(diào)用記錄,只保留 g(3) 的調(diào)用記錄。

這就叫做"尾調(diào)用優(yōu)化"(Tail call optimization),即只保留內(nèi)層函數(shù)的調(diào)用記錄。如果所有函數(shù)都是尾調(diào)用,那么完全可以做到每次執(zhí)行時,調(diào)用記錄只有一項,這將大大節(jié)省內(nèi)存。這就是"尾調(diào)用優(yōu)化"的意義。

三、尾遞歸

函數(shù)調(diào)用自身,稱為遞歸。如果尾調(diào)用自身,就稱為尾遞歸。

遞歸非常耗費內(nèi)存,因為需要同時保存成千上百個調(diào)用記錄,很容易發(fā)生"棧溢出"錯誤(stack overflow)。但對于尾遞歸來說,由于只存在一個調(diào)用記錄,所以永遠不會發(fā)生"棧溢出"錯誤。

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

上面代碼是一個階乘函數(shù),計算n的階乘,最多需要保存n個調(diào)用記錄,復雜度 O(n) 。

如果改寫成尾遞歸,只保留一個調(diào)用記錄,復雜度 O(1) 。

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

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

四、遞歸函數(shù)的改寫

尾遞歸的實現(xiàn),往往需要改寫遞歸函數(shù),確保最后一步只調(diào)用自身。做到這一點的方法,就是把所有用到的內(nèi)部變量改寫成函數(shù)的參數(shù)。比如上面的例子,階乘函數(shù) factorial 需要用到一個中間變量 total ,那就把這個中間變量改寫成函數(shù)的參數(shù)。這樣做的缺點就是不太直觀,第一眼很難看出來,為什么計算5的階乘,需要傳入兩個參數(shù)5和1?

兩個方法可以解決這個問題。方法一是在尾遞歸函數(shù)之外,再提供一個正常形式的函數(shù)。

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

上面代碼通過一個正常形式的階乘函數(shù) factorial ,調(diào)用尾遞歸函數(shù) tailFactorial ,看起來就正常多了。

函數(shù)式編程有一個概念,叫做柯里化(currying),意思是將多參數(shù)的函數(shù)轉(zhuǎn)換成單參數(shù)的形式。這里也可以使用柯里化。

function currying(fn, n) {  return function (m) {    return fn.call(this, m, n);  };}function tailFactorial(n, total) {  if (n === 1) return total;  return tailFactorial(n - 1, n * total);}const factorial = currying(tailFactorial, 1);factorial(5) // 120

上面代碼通過柯里化,將尾遞歸函數(shù) tailFactorial 變?yōu)橹唤邮?個參數(shù)的 factorial 。

第二種方法就簡單多了,就是采用ES6的函數(shù)默認值。

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

上面代碼中,參數(shù) total 有默認值1,所以調(diào)用時不用提供這個值。

總結(jié)一下,遞歸本質(zhì)上是一種循環(huán)操作。純粹的函數(shù)式編程語言沒有循環(huán)操作命令,所有的循環(huán)都用遞歸實現(xiàn),這就是為什么尾遞歸對這些語言極其重要。對于其他支持"尾調(diào)用優(yōu)化"的語言(比如Lua,ES6),只需要知道循環(huán)可以用遞歸代替,而一旦使用遞歸,就最好使用尾遞歸。

([說明] 本文摘自我寫的《ECMAScript 6入門》

五、嚴格模式

ES6的尾調(diào)用優(yōu)化只在嚴格模式下開啟,正常模式是無效的。

這是因為在正常模式下,函數(shù)內(nèi)部有兩個變量,可以跟蹤函數(shù)的調(diào)用棧。

  • arguments:返回調(diào)用時函數(shù)的參數(shù)。

  • func.caller:返回調(diào)用當前函數(shù)的那個函數(shù)。

尾調(diào)用優(yōu)化發(fā)生時,函數(shù)的調(diào)用棧會改寫,因此上面兩個變量就會失真。嚴格模式禁用這兩個變量,所以尾調(diào)用模式僅在嚴格模式下生效。

六、參考鏈接

(完)

from: http://www.ruanyifeng.com/blog/2015/04/tail-call.html

來源:http://www.icode9.com/content-4-117401.html
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Java的遞歸算法
遞歸調(diào)用函數(shù)專題-C語言
遞歸和迭代
C|深入了解函數(shù)調(diào)用與遞歸調(diào)用(遞推、回歸)
簡學Python第三章
用lambda表達式讓Python代碼更簡潔、更優(yōu)雅
更多類似文章 >>
生活服務(wù)
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服