国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript專題之遞歸

asoren / 1931人閱讀

摘要:專題系列第十八篇,講解遞歸和尾遞歸定義程序調(diào)用自身的編程技巧稱為遞歸。然而非尾調(diào)用函數(shù),就會創(chuàng)建多個(gè)執(zhí)行上下文壓入執(zhí)行上下文棧。所以我們只用把階乘函數(shù)改造成一個(gè)尾遞歸形式,就可以避免創(chuàng)建那么多的執(zhí)行上下文。

JavaScript 專題系列第十八篇,講解遞歸和尾遞歸

定義

程序調(diào)用自身的編程技巧稱為遞歸(recursion)。

階乘

以階乘為例:

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

console.log(factorial(5)) // 5 * 4 * 3 * 2 * 1 = 120

示意圖(圖片來自 wwww.penjee.com):

斐波那契數(shù)列

在《JavaScript專題之函數(shù)記憶》中講到過的斐波那契數(shù)列也使用了遞歸:

function fibonacci(n){
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(5)) // 1 1 2 3 5
遞歸條件

從這兩個(gè)例子中,我們可以看出:

構(gòu)成遞歸需具備邊界條件、遞歸前進(jìn)段和遞歸返回段,當(dāng)邊界條件不滿足時(shí),遞歸前進(jìn),當(dāng)邊界條件滿足時(shí),遞歸返回。階乘中的 n == 1 和 斐波那契數(shù)列中的 n < 2 都是邊界條件。

總結(jié)一下遞歸的特點(diǎn):

子問題須與原始問題為同樣的事,且更為簡單;

不能無限制地調(diào)用本身,須有個(gè)出口,化簡為非遞歸狀況處理。

了解這些特點(diǎn)可以幫助我們更好的編寫遞歸函數(shù)。

執(zhí)行上下文棧

在《JavaScript深入之執(zhí)行上下文棧》中,我們知道:

當(dāng)執(zhí)行一個(gè)函數(shù)的時(shí)候,就會創(chuàng)建一個(gè)執(zhí)行上下文,并且壓入執(zhí)行上下文棧,當(dāng)函數(shù)執(zhí)行完畢的時(shí)候,就會將函數(shù)的執(zhí)行上下文從棧中彈出。

試著對階乘函數(shù)分析執(zhí)行的過程,我們會發(fā)現(xiàn),JavaScript 會不停的創(chuàng)建執(zhí)行上下文壓入執(zhí)行上下文棧,對于內(nèi)存而言,維護(hù)這么多的執(zhí)行上下文也是一筆不小的開銷吶!那么,我們該如何優(yōu)化呢?

答案就是尾調(diào)用。

尾調(diào)用

尾調(diào)用,是指函數(shù)內(nèi)部的最后一個(gè)動作是函數(shù)調(diào)用。該調(diào)用的返回值,直接返回給函數(shù)。

舉個(gè)例子:

// 尾調(diào)用
function f(x){
    return g(x);
}

然而

// 非尾調(diào)用
function f(x){
    return g(x) + 1;
}

并不是尾調(diào)用,因?yàn)?g(x) 的返回值還需要跟 1 進(jìn)行計(jì)算后,f(x)才會返回值。

兩者又有什么區(qū)別呢?答案就是執(zhí)行上下文棧的變化不一樣。

為了模擬執(zhí)行上下文棧的行為,讓我們定義執(zhí)行上下文棧是一個(gè)數(shù)組:

    ECStack = [];

我們模擬下第一個(gè)尾調(diào)用函數(shù)執(zhí)行時(shí)的執(zhí)行上下文棧變化:

// 偽代碼
ECStack.push( functionContext);

ECStack.pop();

ECStack.push( functionContext);

ECStack.pop();

我們再來模擬一下第二個(gè)非尾調(diào)用函數(shù)執(zhí)行時(shí)的執(zhí)行上下文棧變化:

ECStack.push( functionContext);

ECStack.push( functionContext);

ECStack.pop();

ECStack.pop();

也就說尾調(diào)用函數(shù)執(zhí)行時(shí),雖然也調(diào)用了一個(gè)函數(shù),但是因?yàn)樵瓉淼牡暮瘮?shù)執(zhí)行完畢,執(zhí)行上下文會被彈出,執(zhí)行上下文棧中相當(dāng)于只多壓入了一個(gè)執(zhí)行上下文。然而非尾調(diào)用函數(shù),就會創(chuàng)建多個(gè)執(zhí)行上下文壓入執(zhí)行上下文棧。

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

所以我們只用把階乘函數(shù)改造成一個(gè)尾遞歸形式,就可以避免創(chuàng)建那么多的執(zhí)行上下文。但是我們該怎么做呢?

階乘函數(shù)優(yōu)化

我們需要做的就是把所有用到的內(nèi)部變量改寫成函數(shù)的參數(shù),以階乘函數(shù)為例:

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

console.log(factorial(4, 1)) // 24

然而這個(gè)很奇怪吶……我們計(jì)算 4 的階乘,結(jié)果函數(shù)要傳入 4 和 1,我就不能只傳入一個(gè) 4 嗎?

這個(gè)時(shí)候就要用到我們在《JavaScript專題之柯里化》中編寫的 curry 函數(shù)了:

var newFactorial = curry(factorial, _, 1)

newFactorial(5) // 24
應(yīng)用

如果你看過 JavaScript 專題系列的文章,你會發(fā)現(xiàn)遞歸有著很多的應(yīng)用。

作為專題系列的第十八篇,我們來盤點(diǎn)下之前的文章中都有哪些涉及到了遞歸:

1.《JavaScript 專題之?dāng)?shù)組扁平化》:

function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}

2.《JavaScript 專題之深淺拷貝》:

var deepCopy = function(obj) {
    if (typeof obj !== "object") return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === "object" ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

3.JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend:

// 非完整版本,完整版本請點(diǎn)擊查看具體的文章
function extend() {

    ...

    // 循環(huán)遍歷要復(fù)制的對象們
    for (; i < length; i++) {
        // 獲取當(dāng)前對象
        options = arguments[i];
        // 要求不能為空 避免extend(a,,b)這種情況
        if (options != null) {
            for (name in options) {
                // 目標(biāo)屬性值
                src = target[name];
                // 要復(fù)制的對象的屬性值
                copy = options[name];

                if (deep && copy && typeof copy == "object") {
                    // 遞歸調(diào)用
                    target[name] = extend(deep, src, copy);
                }
                else if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }

    ...

};

4.《JavaScript 專題之如何判斷兩個(gè)對象相等》:

// 非完整版本,完整版本請點(diǎn)擊查看具體的文章
// 屬于間接調(diào)用
function eq(a, b, aStack, bStack) {

    ...

    // 更復(fù)雜的對象使用 deepEq 函數(shù)進(jìn)行深度比較
    return deepEq(a, b, aStack, bStack);
};

function deepEq(a, b, aStack, bStack) {

    ...

    // 數(shù)組判斷
    if (areArrays) {

        length = a.length;
        if (length !== b.length) return false;

        while (length--) {
            if (!eq(a[length], b[length], aStack, bStack)) return false;
        }
    }
    // 對象判斷
    else {

        var keys = Object.keys(a),
            key;
        length = keys.length;

        if (Object.keys(b).length !== length) return false;
        while (length--) {

            key = keys[length];
            if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
        }
    }

}

5.《JavaScript 專題之函數(shù)柯里化》:

// 非完整版本,完整版本請點(diǎn)擊查看具體的文章
function curry(fn, args) {
    length = fn.length;

    args = args || [];

    return function() {

        var _args = args.slice(0),

            arg, i;

        for (i = 0; i < arguments.length; i++) {

            arg = arguments[i];

            _args.push(arg);

        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}
寫在最后

遞歸的內(nèi)容遠(yuǎn)不止這些,比如還有漢諾塔、二叉樹遍歷等遞歸場景,本篇就不過多展開,真希望未來能寫個(gè)算法系列。

專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。

JavaScript專題系列預(yù)計(jì)寫二十篇左右,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,請?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵(lì)。

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

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/91753.html

相關(guān)文章

  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評論0 收藏0
  • JavaScript專題系列20篇正式完結(jié)!

    摘要:寫在前面專題系列是我寫的第二個(gè)系列,第一個(gè)系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫在前面 JavaScript 專題系列是我寫的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專題系列共計(jì) 20 篇,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評論0 收藏0
  • JavaScript專題數(shù)組扁平化

    摘要:專題系列第九篇,講解如何實(shí)現(xiàn)數(shù)組的扁平化,并解析的源碼扁平化數(shù)組的扁平化,就是將一個(gè)嵌套多層的數(shù)組嵌套可以是任何層數(shù)轉(zhuǎn)換為只有一層的數(shù)組。 JavaScript 專題系列第九篇,講解如何實(shí)現(xiàn)數(shù)組的扁平化,并解析 underscore 的 _.flatten 源碼 扁平化 數(shù)組的扁平化,就是將一個(gè)嵌套多層的數(shù)組 array (嵌套可以是任何層數(shù))轉(zhuǎn)換為只有一層的數(shù)組。 舉個(gè)例子,假設(shè)有個(gè)...

    tuantuan 評論0 收藏0
  • JavaScript專題從零實(shí)現(xiàn)jQuery的extend

    摘要:不過的實(shí)現(xiàn)中,多了很多細(xì)節(jié)上的判斷,比如第一個(gè)參數(shù)是否是布爾值,是否是一個(gè)對象,不傳參數(shù)時(shí)的默認(rèn)值等。 JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 extend 函數(shù) 前言 jQuery 的 extend 是 jQuery 中應(yīng)用非常多的一個(gè)函數(shù),今天我們一邊看 jQuery 的 extend 的特性,一邊實(shí)現(xiàn)一個(gè) extend! extend 基本用...

    wangtdgoodluck 評論0 收藏0
  • JavaScript專題深淺拷貝

    摘要:專題系列第六篇,講解深淺拷貝的技巧和以及實(shí)現(xiàn)深淺拷貝的思路前言拷貝也是面試經(jīng)典吶數(shù)組的淺拷貝如果是數(shù)組,我們可以利用數(shù)組的一些方法比如返回一個(gè)新數(shù)組的特性來實(shí)現(xiàn)拷貝。所以我們可以看出使用和是一種淺拷貝。 JavaScript 專題系列第六篇,講解深淺拷貝的技巧和以及實(shí)現(xiàn)深淺拷貝的思路 前言 拷貝也是面試經(jīng)典吶! 數(shù)組的淺拷貝 如果是數(shù)組,我們可以利用數(shù)組的一些方法比如:slice、co...

    RancherLabs 評論0 收藏0

發(fā)表評論

0條評論

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