成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

翻譯連載 | 第 9 章:遞歸(下)-《JavaScript輕量級函數(shù)式編程》 |《你不知道的JS》

LeviDing / 2766人閱讀

摘要:每個函數(shù)調(diào)用都將開辟出一小塊稱為堆棧幀的內(nèi)存。當?shù)诙€函數(shù)開始執(zhí)行,堆棧幀增加到個。當這個函數(shù)調(diào)用結(jié)束后,它的幀會從堆棧中退出。保持堆棧幀跟蹤函數(shù)調(diào)用的狀態(tài),并將其分派給下一個遞歸調(diào)用迭。

原文地址:Functional-Light-JS

原文作者:Kyle Simpson-《You-Dont-Know-JS》作者

關(guān)于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),是 JavaScript 中最嚴謹?shù)倪壿?。?jīng)過捶打磨練,成就了本書的中文版。本書包含了函數(shù)式編程之精髓,希望可以幫助大家在學習函數(shù)式編程的道路上走的更順暢。比心。

譯者團隊(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao

第 9 章:遞歸(下) 棧、堆

一起看下之前的兩個遞歸函數(shù) isOdd(..)isEven(..)

function isOdd(v) {
    if (v === 0) return false;
    return isEven( Math.abs( v ) - 1 );
}

function isEven(v) {
    if (v === 0) return true;
    return isOdd( Math.abs( v ) - 1 );
}

如果你執(zhí)行下面這行代碼,在大多數(shù)瀏覽器里面都會報錯:

isOdd( 33333 );            // RangeError: Maximum call stack size exceeded

這個錯誤是什么情況?引擎拋出這個錯誤,是因為它試圖保護系統(tǒng)內(nèi)存不會被你的程序耗盡。為了解釋這個問題,我們需要先看看當函數(shù)調(diào)用時JS引擎中發(fā)生了什么。

每個函數(shù)調(diào)用都將開辟出一小塊稱為堆棧幀的內(nèi)存。堆棧幀中包含了函數(shù)語句當前狀態(tài)的某些重要信息,包括任意變量的值。之所以這樣,是因為一個函數(shù)暫停去執(zhí)行另外一個函數(shù),而另外一個函數(shù)運行結(jié)束后,引擎需要返回到之前暫停時候的狀態(tài)繼續(xù)執(zhí)行。

當?shù)诙€函數(shù)開始執(zhí)行,堆棧幀增加到 2 個。如果第二個函數(shù)又調(diào)用了另外一個函數(shù),堆棧幀將增加到 3 個,以此類推?!皸!钡囊馑际?,函數(shù)被它前一個函數(shù)調(diào)用時,這個函數(shù)幀會被“推”到最頂部。當這個函數(shù)調(diào)用結(jié)束后,它的幀會從堆棧中退出。

看下這段程序:

function foo() {
    var z = "foo!";
}

function bar() {
    var y = "bar!";
    foo();
}

function baz() {
    var x = "baz!";
    bar();
}

baz();

來一步步想象下這個程序的堆棧幀:

注意: 如果這些函數(shù)間沒有相互調(diào)用,而只是依次執(zhí)行 -- 比如前一個函數(shù)運行結(jié)束后才開始調(diào)用下一個函數(shù) baz(); bar(); foo(); -- 則堆棧幀并沒有產(chǎn)生;因為在下一個函數(shù)開始之前,上一個函數(shù)運行結(jié)束并把它的幀從堆棧里面移除了。

所以,每一個函數(shù)運行時候,都會占用一些內(nèi)存。對多數(shù)程序來說,這沒什么大不了的,不是嗎?但是,一旦你引用了遞歸,問題就不一樣了。
雖然你幾乎肯定不會在一個調(diào)用棧中手動調(diào)用成千(或數(shù)百)次不同的函數(shù),但你很容易看到產(chǎn)生數(shù)萬個或更多遞歸調(diào)用的堆棧。

當引擎認為調(diào)用棧增加的太多并且應該停止增加時候,它會以主觀的限制來阻止當前步驟,所以 isOdd(..)isEven(..) 函數(shù)拋出了 RangeError 未知錯誤。這不太可能是內(nèi)存接近零時候產(chǎn)生的限制,而是引擎的預測,因為如果這種程序持續(xù)運行下去,內(nèi)存會爆掉的。由于引擎無法判斷一個程序最終是否會停止,所以它必須做出確定的猜測。

引擎的限制因情況而定。規(guī)范里面并沒有任何說明,因此,它也不是 必需的。但如果沒有限制的話,設(shè)備很容易遭到破壞或惡意代碼攻擊,故而幾乎所有的JS引擎都有一個限制。不同的設(shè)備環(huán)境、不同的引擎,會有不同的限制,也就無法預測或保證函數(shù)調(diào)用棧能調(diào)用多少次。

在處理大數(shù)據(jù)量時候,這個限制對于開發(fā)人員來說,會對遞歸的性能有一定的要求。我認為,這種限制也可能是造成開發(fā)人員不喜歡使用遞歸編程的最大原因。
遺憾的是,遞歸編程是一種編程思想而不是主流的編程技術(shù)。

尾調(diào)用

遞歸編程和內(nèi)存限制都要比 JS 技術(shù)出現(xiàn)的早。追溯到上世紀 60 年代,當時開發(fā)人員想使用遞歸編程并希望運行在他們強大的計算機的設(shè)備,而所謂強大計算機的內(nèi)存,尚遠不如我們今天在手表上的內(nèi)存。

幸運的是,在那個希望的原野上,進行了一個有力的觀測。該技術(shù)稱為 尾調(diào)用。

它的思路是如果一個回調(diào)從函數(shù) baz() 轉(zhuǎn)到函數(shù) bar() 時候,而回調(diào)是在函數(shù) baz() 的最底部執(zhí)行 -- 也就是尾調(diào)用 -- 那么 baz() 的堆棧幀就不再需要了。也就意謂著,內(nèi)存可以被回收,或只需簡單的執(zhí)行 bar() 函數(shù)。 如圖所示:

尾調(diào)用并不是遞歸特有的;它適用于任何函數(shù)調(diào)用。但是,在大多數(shù)情況下,你的手動非遞歸調(diào)用棧不太可能超過 10 級,因此尾調(diào)用對你程序內(nèi)存的影響可能相當?shù)汀?/p>

在遞歸的情況下,尾調(diào)用作用很明顯,因為這意味著遞歸堆??梢浴坝肋h”運行下去,唯一的性能問題就是計算,而不再是固定的內(nèi)存限制。在固定的內(nèi)存中尾遞歸可以運行 O(1) (常數(shù)階時間復雜度計算)。

這些技術(shù)通常被稱為尾調(diào)用優(yōu)化(TCO),但重點在于從優(yōu)化技術(shù)中,區(qū)分出在固定內(nèi)存空間中檢測尾調(diào)用運行的能力。從技術(shù)上講,尾調(diào)用并不像大多數(shù)人所想的那樣,它們的運行速度可能比普通回調(diào)還慢。TCO 是關(guān)于把尾調(diào)用更加高效運行的一些優(yōu)化技術(shù)。

正確的尾調(diào)用 (PTC)

在 ES6 出來之前,JavaScript 對尾調(diào)用一直沒明確規(guī)定(也沒有禁用)。ES6 明確規(guī)定了 PTC 的特定形式,在 ES6 中,只要使用尾調(diào)用,就不會發(fā)生棧溢出。實際上這也就意味著,只要正確的使用 PTC,就不會拋出 RangeError 這樣的異常錯誤。

首先,在 JavaScript 中應用 PTC,必須以嚴格模式書寫代碼。如果你以前沒有用過嚴格模式,你得試著用用了。那么,您,應該已經(jīng)在使用嚴格模式了吧???

其次,正確 的尾調(diào)用就像這個樣子:

return foo( .. );

換句話說,函數(shù)調(diào)用應該放在最后一步去執(zhí)行,并且不管返回什么東東,都得有返回( return )。這樣的話,JS 就不再需要當前的堆棧幀了。

下面這些 不能 稱之為 PTC:

foo();
return;

// 或

var x = foo( .. );
return x;

// 或

return 1 + foo( .. );

注意: 一些 JS 引擎 能夠var x = foo(); return x; 自動識別為 return foo();,這樣也可以達到 PTC 的效果。但這畢竟不符合規(guī)范。

foo(..) 運行結(jié)束之后 1+ 這部分才開始執(zhí)行,所以此時的堆棧幀依然存在。

不過,下面這個 PTC:

return x ? foo( .. ) : bar( .. );

x 進行條件判斷之后,或執(zhí)行 foo(..),或執(zhí)行 bar(..),不論執(zhí)行哪個,返回結(jié)果都會被 return 返回掉。這個例子符合 PTC 規(guī)范。

為了避免堆棧增加,PTC 要求所有的遞歸必須是在尾部調(diào)用,因此,二分法遞歸 —— 兩次(或以上)遞歸調(diào)用 —— 是不能實現(xiàn) PTC 的。我們曾在文章的前面部分展示過把二分法遞歸轉(zhuǎn)變?yōu)橄嗷ミf歸的例子。也許我們可以試著化整為零,把多重遞歸拆分成符合 PTC 規(guī)范的單個函數(shù)回調(diào)。

重構(gòu)遞歸

如果你想用遞歸來處理問題,卻又超出了 JS 引擎的內(nèi)存堆棧,這時候就需要重構(gòu)下你的遞歸調(diào)用,使它能夠符合 PTC 規(guī)范(或著避免嵌套調(diào)用)。這里有一些重構(gòu)方法也許可以用到,但需要根據(jù)實際情況權(quán)衡。

可讀性強的代碼,是我們的終級目標 —— 謹記,謹記。如果使用遞歸后會造成代碼難以閱讀/理解,那就 不要使用遞歸;換個容易理解的方法吧。

更換堆棧

對遞歸來說,最主要的問題是它的內(nèi)存使用情況。保持堆棧幀跟蹤函數(shù)調(diào)用的狀態(tài),并將其分派給下一個遞歸調(diào)用迭。如果我們弄清楚了如何重新排列我們的遞歸,就可以用 PTC 實現(xiàn)遞歸,并利用 JS 引擎對尾調(diào)用的優(yōu)化處理,那么我們就不用在內(nèi)存中保留當前的堆棧幀了。

來回顧下之前用到的一個求和的例子:

function sum(num1,...nums) {
    if (nums.length == 0) return num1;
    return num1 + sum( ...nums );
}

這個例子并不符合 PTC 規(guī)范。sum(...nums) 運行結(jié)束之后,num1sum(...nums) 的運行結(jié)果進行了累加。這樣的話,當其余參數(shù) ...nums 再次進行遞歸調(diào)用時候,為了得到其與 num1 累加的結(jié)果,必須要保留上一次遞歸調(diào)用的堆棧幀。

重構(gòu)策略的關(guān)鍵點在于,我們可以通過把 置后 處理累加改為 提前 處理,來消除對堆棧的依賴,然后將該部分結(jié)果作為參數(shù)傳遞到遞歸調(diào)用。換句話說,我們不用在當前運用函數(shù)的堆棧幀中保留 num1 + sum(...num1) 的總和,而是把它傳遞到下一個遞歸的堆棧幀中,這樣就能釋放當前遞歸的堆棧幀。

開始之前,我們做些改動:把部分結(jié)果作為一個新的第一個參數(shù)傳入到函數(shù) sum(..)

function sum(result,num1,...nums) {
    // ..
}

這次我們先把 resultnum1 提前計算,然后把 result 作為參數(shù)一起傳入:

"use strict";

function sum(result,num1,...nums) {
    result = result + num1;
    if (nums.length == 0) return result;
    return sum( result, ...nums );
}

現(xiàn)在 sum(..) 已經(jīng)符合 PTC 優(yōu)化規(guī)范了!耶!

但是還有一個缺點,我們修改了函數(shù)的參數(shù)傳遞形式后,用法就跟以前不一樣了。調(diào)用者不得不在需要求和的那些參數(shù)的前面,再傳遞一個 0 作為第一個參數(shù)。

sum( /*initialResult=*/0, 3, 1, 17, 94, 8 );        // 123

這就尷尬了。

通常,大家的處理方式是,把這個尷尬的遞歸函數(shù)重新命名,然后定義一個接口函數(shù)把問題隱藏起來:

"use strict";

function sumRec(result,num1,...nums) {
    result = result + num1;
    if (nums.length == 0) return result;
    return sumRec( result, ...nums );
}

function sum(...nums) {
    return sumRec( /*initialResult=*/0, ...nums );
}

sum( 3, 1, 17, 94, 8 );                                // 123

情況好了些。但依然有問題:之前只需要一個函數(shù)就能解決的事,現(xiàn)在我們用了兩個。有時候你會發(fā)現(xiàn),在處理這類問題上,有些開發(fā)者用內(nèi)部函數(shù)把遞歸 “藏了起來”:

"use strict";

function sum(...nums) {
    return sumRec( /*initialResult=*/0, ...nums );

    function sumRec(result,num1,...nums) {
        result = result + num1;
        if (nums.length == 0) return result;
        return sumRec( result, ...nums );
    }
}

sum( 3, 1, 17, 94, 8 );                                // 123

這個方法的缺點是,每次調(diào)用外部函數(shù) sum(..),我們都得重新創(chuàng)建內(nèi)部函數(shù) sumRec(..)。我們可以把他們平級放置在立即執(zhí)行的函數(shù)中,只暴露出我們想要的那個的函數(shù):

"use strict";

var sum = (function IIFE(){

    return function sum(...nums) {
        return sumRec( /*initialResult=*/0, ...nums );
    }

    function sumRec(result,num1,...nums) {
        result = result + num1;
        if (nums.length == 0) return result;
        return sumRec( result, ...nums );
    }

})();

sum( 3, 1, 17, 94, 8 );                                // 123

好啦,現(xiàn)在即符合了 PTC 規(guī)范,又保證了 sum(..) 參數(shù)的整潔性,調(diào)用者不需要了解函數(shù)的內(nèi)部實現(xiàn)細節(jié)。完美!

可是...天吶,本來是簡單的遞歸函數(shù),現(xiàn)在卻出現(xiàn)了很多噪點??勺x性已經(jīng)明顯降低。至少說,這是不成功的。有些時候,這只是我們能做的最好的。

幸運的事,在一些其它的例子中,比如上一個例子,有一個比較好的方式。一起重新看下:

"use strict";

function sum(result,num1,...nums) {
    result = result + num1;
    if (nums.length == 0) return result;
    return sum( result, ...nums );
}

sum( /*initialResult=*/0, 3, 1, 17, 94, 8 );        // 123

也許你會注意到,result 就像 num1 一樣,也就是說,我們可以把列表中的第一個數(shù)字作為我們的運行總和;這甚至包括了第一次調(diào)用的情況。我們需要的是重新命名這些參數(shù),使函數(shù)清晰可讀:

"use strict";

function sum(num1,num2,...nums) {
    num1 = num1 + num2;
    if (nums.length == 0) return num1;
    return sum( num1, ...nums );
}

sum( 3, 1, 17, 94, 8 );                                // 123

帥呆了。比之前好了很多,嗯?!我認為這種模式在聲明/合理和執(zhí)行之間達到了很好的平衡。

讓我們試著重構(gòu)下前面的 maxEven(..)(目前還不符合 PTC 規(guī)范)。就像之前我們把參數(shù)的和作為第一個參數(shù)一樣,我們可以依次減少列表中的數(shù)字,同時一直把遇到的最大偶數(shù)作為第一個參數(shù)。

為了清楚起見,我們可能使用算法策略(類似于我們之前討論過的):

首先對前兩個參數(shù) num1num2 進行對比。

如果 num1 是偶數(shù),并且 num1 大于 num2,num1 保持不變。

如果 num2 是偶數(shù),把 num2 賦值給 num1

否則的話,num1 等于 undefined

如果除了這兩個參數(shù)之外,還存在其它參數(shù) nums,把它們與 num1 進行遞歸對比。

最后,不管是什么值,只需返回 num1

依照上面的步驟,代碼如下:

"use strict";

function maxEven(num1,num2,...nums) {
    num1 =
        (num1 % 2 == 0 && !(maxEven( num2 ) > num1)) ?
            num1 :
            (num2 % 2 == 0 ? num2 : undefined);

    return nums.length == 0 ?
        num1 :
        maxEven( num1, ...nums )
}

注意: 函數(shù)第一次調(diào)用 maxEven(..) 并不是為了 PTC 優(yōu)化,當它只傳遞 num2 時,只遞歸一級就返回了;它只是一個避免重復 邏輯的技巧。因此,只要該調(diào)用是完全不同的函數(shù),就不會增加遞歸堆棧。第二次調(diào)用 maxEven(..) 是基于 PTC 優(yōu)化角度的真正遞歸調(diào)用,因此不會隨著遞歸的進行而造成堆棧的增加。

重申下,此示例僅用于說明將遞歸轉(zhuǎn)化為符合 PTC 規(guī)范以優(yōu)化堆棧(內(nèi)存)使用的方法。求最大偶數(shù)值的更直接方法可能是,先對參數(shù)列表中的 nums 過濾,然后冒泡或排序處理。

基于 PTC 重構(gòu)遞歸,固然對簡單的聲明形式有一些影響,但依然有理由去做這樣的事。不幸的是,存在一些遞歸,即使我們使用了接口函數(shù)來擴展,也不會很好,因此,我們需要有不同的思路。

后繼傳遞格式 (CPS)

在 JavaScript 中, continuation 一詞通常用于表示在某個函數(shù)完成后指定需要執(zhí)行的下一個步驟的回調(diào)函數(shù)。組織代碼,使得每個函數(shù)在其結(jié)束時接收另一個執(zhí)行函數(shù),被稱為后繼傳遞格式(CPS)。

有些形式的遞歸,實際上是無法按照純粹的 PTC 規(guī)范重構(gòu)的,特別是相互遞歸。我們之前提到過的 fib(..) 函數(shù),以及我們派生出來的相互遞歸形式。這兩個情況,皆是存在多個遞歸調(diào)用,這些遞歸調(diào)用阻礙了 PTC 內(nèi)存優(yōu)化。

但是,你可以執(zhí)行第一個遞歸調(diào)用,并將后續(xù)遞歸調(diào)用包含在后續(xù)函數(shù)中并傳遞到第一個調(diào)用。盡管這意味著最終需要在堆棧中執(zhí)行更多的函數(shù),但由于后繼函數(shù)所包含的都是 PTC 形式的,所以堆棧內(nèi)存的使用情況不會無限增長。

fib(..) 做如下修改:

"use strict";

function fib(n,cont = identity) {
    if (n <= 1) return cont( n );
    return fib(
        n - 2,
        n2 => fib(
            n - 1,
            n1 => cont( n2 + n1 )
        )
    );
}

仔細看下都做了哪些事情。首先,我們默認用了第三章中的 cont(..) 后繼函數(shù)表示 identity(..);記住,它只簡單的返回傳遞給它的任何東西。

更重要的是,這里面增加了不僅僅是一個而是兩個后續(xù)函數(shù)。第一個后續(xù)函數(shù)接收 fib(n-2) 的運行結(jié)果作為參數(shù) n2。第二個內(nèi)部后續(xù)函數(shù)接收 fib(n-1)的運行結(jié)果作為參數(shù) n1。當?shù)玫?n1n2 的值后,兩者再相加 (n2 + n1),相加的運行結(jié)果會傳入到下一個后續(xù)函數(shù) cont(..)。

也許這將有助于我們梳理下流程:就像我們之前討論的,在遞歸堆棧之后,當我們傳遞部分結(jié)果而不是返回它們時,每一步都被包含在一個后續(xù)函數(shù)中,這拖慢了計算速度。這個技巧允許我們執(zhí)行多個符合 PTC 規(guī)范的步驟。

在靜態(tài)語言中,CPS通常為尾調(diào)用提供了編譯器可以自動識別并重新排列遞歸代碼以利用的機會。很可惜,不能用在原生 JS 上。

在 JavaScript 中,你得自己書寫出符合 CPS 格式的代碼。這并不是明智的做法;以命令符號聲明的形式肯定會讓內(nèi)容有些不清楚。 但總的來說,這種形式仍然要比 for 循環(huán)更具有聲明性。

警告: 我們需要注意的一個比較重要的事項是,在 CPS 中,創(chuàng)建額外的內(nèi)部后續(xù)函數(shù)仍然消耗內(nèi)存,但有些不同。并不是之前的堆棧幀累積,閉包只是消耗多余的內(nèi)存空間(一般情況下,是堆棧里面的多余內(nèi)存空間)。在這些情況下,引擎似乎沒有啟動 RangeError 限制,但這并不意味著你的內(nèi)存使用量是按比例固定好的。

彈簧床

除了 CPS 后續(xù)傳遞格式之外,另外一種內(nèi)存優(yōu)化的技術(shù)稱為彈簧床。在彈簧床格式的代碼中,同樣的創(chuàng)建了類似 CPS 的后續(xù)函數(shù),不同的是,它們沒有被傳遞,而是被簡單的返回了。

不再是函數(shù)調(diào)用另外的函數(shù),堆棧的深度也不會大于一層,因為每個函數(shù)只會返回下一個將調(diào)用的函數(shù)。循環(huán)只是繼續(xù)運行每個返回的函數(shù),直到再也沒有函數(shù)可運行。

彈簧床的優(yōu)點之一是在非 PTC 環(huán)境下你一樣可以應用此技術(shù)。另一個優(yōu)點是每個函數(shù)都是正常調(diào)用,而不是 PTC 優(yōu)化,所以它可以運行得更快。

一起來試下 trampoline(..)

function trampoline(fn) {
    return function trampolined(...args) {
        var result = fn( ...args );

        while (typeof result == "function") {
            result = result();
        }

        return result;
    };
}

當返回一個函數(shù)時,循環(huán)繼續(xù),執(zhí)行該函數(shù)并返回其運行結(jié)果,然后檢查返回結(jié)果的類型。一旦返回的結(jié)果類型不是函數(shù),彈簧床就認為函數(shù)調(diào)用完成了并返回結(jié)果值。

所以我們可能需要使用前面講到的,將部分結(jié)果作為參數(shù)傳遞的技巧。以下是我們在之前的數(shù)組求和中使用此技巧的示例:

var sum = trampoline(
    function sum(num1,num2,...nums) {
        num1 = num1 + num2;
        if (nums.length == 0) return num1;
        return () => sum( num1, ...nums );
    }
);

var xs = [];
for (let i=0; i<20000; i++) {
    xs.push( i );
}

sum( ...xs );                    // 199990000

缺點是你需要將遞歸函數(shù)包裹在執(zhí)行彈簧床功能的函數(shù)中; 此外,就像 CPS 一樣,需要為每個后續(xù)函數(shù)創(chuàng)建閉包。然而,與 CPS 不一樣的地方是,每個返回的后續(xù)數(shù)數(shù),運行并立即完成,所以,當調(diào)用堆棧的深度用盡時,引擎中不會累積越來越多的閉包。

除了執(zhí)行和記憶性能之外,彈簧床技術(shù)優(yōu)于CPS的優(yōu)點是它們在聲明遞歸形式上的侵入性更小,由于你不必為了接收后續(xù)函數(shù)的參數(shù)而更改函數(shù)參數(shù),所以除了執(zhí)行和內(nèi)存性能之外,彈簧床技術(shù)優(yōu)于 CPS 的地方還有,它們在聲明遞歸形式上侵入性更小。雖然彈簧床技術(shù)并不是理想的,但它們可以有效地在命令循環(huán)代碼和聲明性遞歸之間達到平衡。

總結(jié)

遞歸,是指函數(shù)遞歸調(diào)用自身。呃,這就是遞歸的定義。明白了吧!?

直遞歸是指對自身至少調(diào)用一次,直到滿足基本條件才能停止調(diào)用。多重遞歸(像二分遞歸)是指對自身進行多次調(diào)用。相互遞歸是當兩個或以上函數(shù)循環(huán)遞歸 相互 調(diào)用。而遞歸的優(yōu)點是它更具聲明性,因此通常更易于閱讀。

遞歸的優(yōu)點是它更具聲明性,因此通常更易于閱讀。缺點通常是性能方面,但是相比執(zhí)行速度,更多的限制在于內(nèi)存方面。

尾調(diào)用是通過減少或釋放堆棧幀來節(jié)約內(nèi)存空間。要在 JavaScript 中實現(xiàn)尾調(diào)用 “優(yōu)化”,需要基于嚴格模式和適當?shù)奈舱{(diào)用( PTC )。我們也可以混合幾種技術(shù)來將非 PTC 遞歸函數(shù)重構(gòu)為 PTC 格式,或者至少能通過平鋪堆棧來節(jié)約內(nèi)存空間。

謹記:遞歸應該使代碼更容易讀懂。如果你誤用或濫用遞歸,代碼的可讀性將會比命令形式更糟。千萬不要這樣做。

【上一章】翻譯連載 | 第 9 章:遞歸(上)-《JavaScript輕量級函數(shù)式編程》 |《你不知道的JS》姊妹篇

iKcamp原創(chuàng)新書《移動Web前端高效開發(fā)實戰(zhàn)》已在亞馬遜、京東、當當開售。

滬江Web前端上海團隊招聘【W(wǎng)eb前端架構(gòu)師】,有意者簡歷至:[email protected]

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88665.html

相關(guān)文章

  • 翻譯連載 | 9 遞歸(上)-《JavaScript量級函數(shù)編程》 |《你不知道JS

    摘要:一旦我們滿足了基本條件值為,我們將不再調(diào)用遞歸函數(shù),只是有效地執(zhí)行了。遞歸深諳函數(shù)式編程之精髓,最被廣泛引證的原因是,在調(diào)用棧中,遞歸把大部分顯式狀態(tài)跟蹤換為了隱式狀態(tài)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;...

    MasonEast 評論0 收藏0
  • 翻譯連載 |《你不知道JS》姊妹篇 |《JavaScript 量級函數(shù)編程》- 引言&前言

    摘要:我稱之為輕量級函數(shù)式編程。序眾所周知,我是一個函數(shù)式編程迷。函數(shù)式編程有很多種定義。本書是你開啟函數(shù)式編程旅途的絕佳起點。事實上,已經(jīng)有很多從頭到尾正確的方式介紹函數(shù)式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 譯者團隊(排名不分先后):阿希、blueken、brucecham、...

    2bdenny 評論0 收藏0
  • 翻譯連載 | JavaScript量級函數(shù)編程-4:組合函數(shù) |《你不知道JS》姊妹篇

    摘要:把數(shù)據(jù)的流向想象成糖果工廠的一條傳送帶,每一次操作其實都是冷卻切割包裝糖果中的一步。在該章節(jié)中,我們將會用糖果工廠的類比來解釋什么是組合。糖果工廠靠這套流程運營的很成功,但是和所有的商業(yè)公司一樣,管理者們需要不停的尋找增長點。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌...

    JowayYoung 評論0 收藏0
  • 翻譯連載 | 10 :異步函數(shù)(上)-《JavaScript量級函數(shù)編程》 |《你不

    摘要:這就是積極的函數(shù)式編程。上一章翻譯連載第章遞歸下輕量級函數(shù)式編程你不知道的姊妹篇原創(chuàng)新書移動前端高效開發(fā)實戰(zhàn)已在亞馬遜京東當當開售。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總...

    Lucky_Boy 評論0 收藏0
  • 翻譯連載 |《你不知道JS》姊妹篇 |《JavaScript 量級函數(shù)編程》- 1

    摘要:所以我覺得函數(shù)式編程領(lǐng)域更像學者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過了深入的研究和審查,并且可以被驗證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會認為形式主義本身有助于學習。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液...

    omgdog 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<