理解js閉包

總結:閉包的核心是[[scope]]屬性,在函數解析過程當中,若是函數引用了外層函數的變量,那麼外層函數(即便自身被銷燬)的活動對象帶着對應變量將會被保留,而且記錄在scope屬性中,做爲做用域鏈的第二層,若是還引用了外層函數的外層函數的變量,那麼對應的活動對象與變量也會被保留,並記錄,將會做爲做用域鏈的第三層,依次類推...。當函數被調用時,所取到的外部變量的值將會是調用時各個變量的值。即當前值。

閉包調用時也是普通函數,只不過做用域鏈多了閉包Closure的成分 數組

閉包的用途:1.模仿塊級做用域,在當即調用函數中聲明內部須要使用的變量;2.管理私有變量和方法;3.函數柯里化閉包

來個極端的例子函數

function closure() {
    let result = [], count = 1
    setInterval(() => {count++}, 1000)
    function outer() {
        let doufu = 'wu'
        return function inner() {
            let foo = 'foo'
            return function closureCallback() {
                let bar = {bar: 'bar'}, bar1 = 'bar'
                result[i] = function () {
                    console.log(count)
                    let b = doufu
                    return bar.bar + i
                }
            }
        }
    }
    let inner = outer()
    let closureCallback = inner()
    for (var i=0; i<10; i++) {
        // [[scope]]包含closure{result, i}
        // 函數在這裏當即調用
        // i爲實時值0,1,2,3...
        closureCallback()
    }
    return result
}

調用結果圖
從圖中能夠看到,最終返回結果分別引用了closureCallback, outer,closure中的變量,因此,在做用域鏈中會保存這三個函數的活動對象,不一樣時間調用,返回的count值不一樣,說明引用的是當前值。
如下是手工示意圖學習

scope構成示意圖

如下是個人學習過程
JavaScript裏面的閉包,指的是函數與聲明該函數的詞法環境的組合。
通常在函數裏面聲明函數,而且引用外面函數的變量,就會產生閉包。定義在全局的時候,默認不產生閉包(所謂閉包,就是當內部函數引用了外部函數中的變量時,會在函數的做用域上面添加一層,這一層包含了函數所引用的外部函數的變量,存放在scope屬性裏面,在調用時,用於造成做用域鏈)
函數在執行時,會在內存中建立一個活動對象,該活動對象包含arguments以及一些參數。並經過複製[[scope]]屬性中的對象構建起執行環境的做用域鏈。this

var bar = 'zoo'
function Foo() {
    this.bar = bar
}

全局

function foo() {
    let bar = 'zoo'
    function zoo() {
        let zoo = bar
    }
}
foo()

函數內

function closure() {
    let result = []
    for (var i=0; i<10; i++) {
        result[i] = function () {
            return i
        }
    }
    return result
}
let arr = closure()

主要體如今函數返回函數,函數A在調用函數B時被建立並從B函數的內部返回。當咱們調用A函數的時候,B函數的做用域鏈已經從內存中銷燬,可是咱們仍然能夠在A中訪問B中存在的變量。由於B中的變量仍然保存在A的活動對象中(做用域鏈中[[scope]]對象裏面)
此時,函數A與A的scope構成closure函數的閉包實例spa

scope
從圖中能夠看到,ar[0](如下稱爲函數A)函數的做用域鏈最頂層爲自身活動對象(arguments, caller, length, name等等構成)再往上則是由一個Closure對象實例構成,能夠看到這一層裏面只包含一個變量i,即建立A時外層函數裏面聲明的變量i。當咱們調用A時,咱們在第二層做用域鏈上面找到的這個i變量。
爲何arr數組每一項都返回的是10,而不是對應的下標值的緣由就在這裏:當咱們調用數組項函數時,遇到變量i,而且在第二層做用域鏈讀取到i,這裏面保存的i就是closure函數裏面定義的i。在A調用時,closure已經執完畢,在closure執行的過程當中i的值從0變到了10。這個性質相似於把本來存在於closure函數中的變量,在closure函數執行完畢後(從內存中移除)咱們能夠在A自身的scope屬性裏訪問到。
簡單一點說就是咱們在調用A函數的時候,訪問到的i變量,是函數closure(雖然它不在了可是它的變量還在,仍然被scope屬性引用。)的當前值(實時值)code

因此要達到預期目標,咱們只須要保證它的scope對象中保存的這個‘i’值是正確的就能夠了。
這裏面的思路就是,函數A被當作普通函數調用時,非閉包狀況下,做用域就是自身(沒有i變量)+ 全局做用域(也沒有),因此這裏仍是須要藉助閉包。即須要保證A上一層做用域的i是正確的值對象

1.建立另一個閉包,每一個i的值都會建立一個閉包ip

function closureCallback(i) {
    // 返回函數裏面的i變量就是closureCallback函數的參數i
    return function() {
        return i
    }
}

function closure() {
    let result = [], b = 'closure'
    for (var i=0; i<10; i++) {
                // 這裏的closureCallback做爲普通函數調用
                // 且沒有引用closure函數的變量,
                // 函數做用域內的變量,沒法直接在函數外取得
                // 因此做用域鏈不包含closure
                // 因此在closureCallback函數[[scope]]中不會有closure函數
        result[i] = closureCallback(i)
    }
    return result
}

2.使用匿名閉包內存

function closure() {
    let result = [], b = 'closure'
    for (var i=0; i<10; i++) {
        result[i] = (function (i) {
            return () => i
        })(i)
    }
    return result
}
let arr = closure()

3.使用let,減小閉包,let具備塊級做用域的效果

function closure() {
    let result = [], b = 'closure'
    for (var i=0; i<10; i++) {
        let j = i
        result[i] = function () {
            return j
        }
    }
    return result
}
let arr = closure()

let造成塊級做用域

相關文章
相關標籤/搜索