攻克-✪-JavaScript中閉包與遞歸

1. 閉包

閉包就是一個函數捕獲作用域內外部綁定

  • 函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中稱爲“閉包” ----《JavaScript權威指南》

  • 閉包是指有權訪問另一個函數作用域中的變量的函數。 ---- 《Javascript高級程序設計》

  • 當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用 域之外執行。— 《你不知道的JavaScript(上)》

2. 常見閉包兩種形式

2.1 函數作爲返回值

    function connectTest() {
        var a = 'OK'

        function childFun(childParam) {
            return "haha~~~" + a + childParam
        }
        return childFun
    }
    var report = connectTest()
    console.log(report(''))

函數childFun()的詞法作用域能夠\color{#FF0000}{訪問}connectTest()的內部作用域。然後將childFun\color{#FF0000}{函數childFun當做一個值類型}進行傳遞。

在函數connectTest執行後,其返回值(函數childFun)賦值給report 對象。

當打印執行report()的時候,實際是通過不同的標識符引用調用了內部函數childFunchildFun\color{#FF0000}{函數childFun在自己定義的詞法作用域以外的地方執行}

在代碼最下方延時一下還會打印麼?

    setTimeout(() => {
        console.log(report('!!!'))
    }, 3000)

js中有垃圾回收器,函數等執行完之後會釋放不再使用的內存空間。當執行完connectTest(),按道理會被銷燬,但是由於函數childFun所在,使得該作用域能夠一直存活,以供childFun之後任何時間進行引用。使\color{#FF0000}{相當於把局部變量駐留在內存中,避免使用了全局變量}。但每一個新的閉包都會捕獲不一樣的值!

2.2 函數作爲參數傳遞

    function connectTest() {
        var a = 'OK'

        function childFun(childParam) {
            return "haha~~~" + a + childParam
        }
        return childFun
    }
    var report = connectTest()

    function test(fn) {
        var a = 'test'
        fn('')
        console.log(fn('!!!test'))  // haha~~~OK!!!test
    }
    test(report)

3. 閉包優缺點

優點:

  • 避免全局變量的污染【阻止父作用域被銷燬,沒有被垃圾回收器釋放內存】
  • 變量儘量私有化

缺點:

  • 使用不佳內存泄露 ,降低程序的性能【駐留在內存中,增大內存使用率】

4. 常見閉包的場景

4.1 回調函數

在定時器、事件監聽器、Ajax 請求、跨窗口通信、Web Workers 或者任何其他的異步(或者同步)任務中,只要使用了回調函數,實際上就是在使用閉包!

4.2 模塊化

爲了最大限度減少暴露捕獲變量的風險,JS經常用下面這種模式

    var dateYear = (function () {
        var year = 2019;
        return {
            lastYear: function (n) {
                return year -= n;
            },
            futureYear: function (n) {
                return year += n;
            }
        }
    })()
    var num = 3;
    console.log(dateYear.year) // undefined
    console.log('2019的前面' + num + '年是:' + dateYear.lastYear(num))
    // console.log('2019的後面' + num + '年是:' + dateYear.futureYear(num))

此時year是私有變量,只有這兩個函數可以調用,其他任何手段都是無法進行訪問,閉包模式提過強大的訪問保護。

4.3 循環中的問題

    for (var i = 0; i < 5; i++) {
        setTimeout(() => {
            console.log(i)
        }, 1000);
    };

輸出的結果是// 5 5 5 5 5.
解釋一下爲什麼5個5,延遲函數的會調會在循環結束時才執行,此時循環已經在5了。

如何需要修改

    for (var i = 0; i < 5; i++) {
        (function () {
            var j = i;
            setTimeout(function timer() {
                console.log(j);
            }, j * 1000);
        })();
        // 修改完善一下
        // (function (j) {
        //     setTimeout(function timer() {
        //         console.log(j);
        //     }, j * 1000);
        // })(i);
    }

此時打印// 0 1 2 3 4
方案二,當然將var換成let,就不存在這回事兒了,【所以說讓let代替var】

    for (let i = 0; i < 5; i++) {
        setTimeout(() => {
            console.log(i)
        }, 1000);
    };

到此差不多對閉包已經認識了,可以在自己開發的項目中看看有那些閉包~~~

5 遞歸

5.1 遞歸的定義

遞歸通常涉及函數調用自身。

遞歸常見的兩種方式

a. 自身直接調用自身的方法或函數

function searchSelf(someProperty) {
 searchSelf(someProperty)
}

b. 間接調用自身的函數

function searchSelf(someProperty) {
 myself(someProperty)
}
function myself(someProperty) {
 searchSelf(someProperty)
}

每個遞歸函數都必須要有邊界條件,即一個停止的條件,以防止無限遞歸(同時瀏覽器
會拋出棧溢出錯誤 stack overflow error)

遞歸併不比普通版本更快,反而更慢。但遞歸容易理解,而且它所需的代面量少。

ES6中使用了尾掉用優化,讓遞歸不會更慢。
(相關尾掉用的參考)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章