欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

js尾調用優(yōu)化的實現(xiàn)

 更新時間:2019年05月23日 09:25:01   作者:阮一峰  
這篇文章主要介紹了js尾調用優(yōu)化的實現(xiàn),尾調用(Tail Call)是函數(shù)式編程的一個重要概念,本文介紹它的含義和用法。感興趣的可以了解一下

尾調用(Tail Call)是函數(shù)式編程的一個重要概念,本文介紹它的含義和用法。

一、什么是尾調用?

尾調用的概念非常簡單,一句話就能說清楚,就是指某個函數(shù)的最后一步是調用另一個函數(shù)。

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

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

以下兩種情況,都不屬于尾調用。

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

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

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

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

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

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

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

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

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

尾調用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調用記錄,因為調用位置、內部變量等信息都不會再用到了,只要直接用內層函數(shù)的調用記錄,取代外層函數(shù)的調用記錄就可以了。

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

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

// 等同于
g(3);

上面代碼中,如果函數(shù)g不是尾調用,函數(shù)f就需要保存內部變量m和n的值、g的調用位置等信息。但由于調用g之后,函數(shù)f就結束了,所以執(zhí)行到最后一步,完全可以刪除 f() 的調用記錄,只保留 g(3) 的調用記錄。

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

三、尾遞歸

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

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

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

factorial(5) // 120

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

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

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

factorial(5, 1) // 120

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

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

尾遞歸的實現(xiàn),往往需要改寫遞歸函數(shù),確保最后一步只調用自身。做到這一點的方法,就是把所有用到的內部變量改寫成函數(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 ,調用尾遞歸函數(shù) tailFactorial ,看起來就正常多了。

函數(shù)式編程有一個概念,叫做柯里化(currying),意思是將多參數(shù)的函數(shù)轉換成單參數(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,所以調用時不用提供這個值。

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

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

五、嚴格模式

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

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

  • arguments:返回調用時函數(shù)的參數(shù)。
  • func.caller:返回調用當前函數(shù)的那個函數(shù)。

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • echarts餅圖labelLine線的長度自適應設置

    echarts餅圖labelLine線的長度自適應設置

    這篇文章主要給大家介紹了關于echarts餅圖labelLine線的長度自適應設置的相關資料,在echarts中,餅圖的標簽線可以通過設置 labelLine屬性來自定義位置,需要的朋友可以參考下
    2023-08-08
  • 那些項目中常見的TypeScript錯誤總結

    那些項目中常見的TypeScript錯誤總結

    這篇文章主要給大家總結介紹了一些項目中常見的TypeScript錯誤的相關資料,如果你想查看所有的錯誤信息和錯誤碼,可以瀏覽TypeScript的源代碼倉庫,當然隨著?ts?版本的更新,官網也會逐漸增加更多新的類型錯誤,需要的朋友可以參考下
    2022-03-03
  • JavaScript的new date等日期函數(shù)在safari中遇到的坑

    JavaScript的new date等日期函數(shù)在safari中遇到的坑

    safari中對于JavaScript的new Date函數(shù)的支持有一個比較奇怪的問題,帶著這個奇怪的問題我們通過本文一起學習吧
    2016-10-10
  • JavaScript?排他思想的具體實現(xiàn)

    JavaScript?排他思想的具體實現(xiàn)

    排他思想的算法就是排除掉其他的,本文主要介紹了JavaScript?排他思想的實現(xiàn),以及介紹了兩個示例,感興趣的可以了解一下
    2021-11-11
  • layui 監(jiān)聽彈窗關閉并刷新父級table的場景分析

    layui 監(jiān)聽彈窗關閉并刷新父級table的場景分析

    這篇文章主要介紹了layui 監(jiān)聽彈窗關閉并刷新父級table的場景分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-07-07
  • TypeScript類型斷言VS類型守衛(wèi)示例詳解

    TypeScript類型斷言VS類型守衛(wèi)示例詳解

    這篇文章主要為大家介紹了TypeScript類型斷言VS類型守衛(wèi)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • JS實現(xiàn)Date日期格式的本地化的方法小結

    JS實現(xiàn)Date日期格式的本地化的方法小結

    為了更好的更新多語言日期的顯示,所以希望實現(xiàn)日期的本地化格式顯示要求,常規(guī)的特殊字符型格式化無法滿足顯示要求,這里整理了幾種我思考實現(xiàn)的本地化實現(xiàn)功能
    2024-06-06
  • JS拉起或下載app的實現(xiàn)代碼

    JS拉起或下載app的實現(xiàn)代碼

    最近做項目遇到這樣的需求,通過手機網頁判斷是否安裝了自己公司app,如果安裝了則拉起app,沒有安裝則跳轉到下載頁。怎么實現(xiàn)呢?下面小編給大家分享js拉起或下載app的實現(xiàn)代碼,需要的朋友參考下
    2017-02-02
  • Bootstrap table的使用方法

    Bootstrap table的使用方法

    這篇文章主要為大家詳細解析了JS組件Bootstrap Table使用方法,具有一定的實用性和參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • javascript簡易畫板開發(fā)

    javascript簡易畫板開發(fā)

    這篇文章主要為大家詳細介紹了javascript簡易畫板開發(fā)代碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10

最新評論