一塊兒來了解一下閉包吧

做用域

做用域是靜態的,在你寫代碼的時候就肯定了。javascript

  • 做用域分爲全局做用域和局部做用域。
  • 全局做用域:任何地方都能訪問到的對象擁有全局做用域。
  • 局部做用域:只在固定的代碼片斷內可訪問到,最多見的例如函數內部。
  • 做用域鏈:當你查找一個變量的時候,先在自身做用域中查找,沒找到就像上級函數做用域中依次查找,這個過程就會造成一個做用域鏈。

執行上下文

執行上下文能夠理解爲當前代碼的執行環境,執行上下文是動態的,在執行調用的時候纔會產生。java

  • 每一個執行上下文包括變量對象(VO)、做用域鏈(ScopeeChain)、this指針。
  • 執行上下文的生命週期能夠分爲兩個階段:建立階段(執行上下文會分別建立VO,ScopeChain以及肯定this指向)、執行階段(完成變量賦值,函數引用以及執行其餘代碼)。

什麼是閉包

  • 在javascript高級程序設計中的定義:閉包是指有權訪問另外一個函數做用域中變量的函數。
  • 函數做用域和函數執行的做用域不在同一做用域就是閉包。
  • 閉包是鏈接函數內部和函數外部的橋樑,函數外部能夠訪問到函數內部的變量(好比:函數做爲另外一個函數的返回值)
  • 閉包的實現主要思路是返回一個函數,因此閉包是典型的高階函數。

閉包產生的條件

  • 函數嵌套
  • 內部函數引用了外部函數的變量

閉包的生命週期

  • 產生:在嵌套內部函數定義執行完的時候閉包就產生了,過程以下:
function fn1() {
    //函數提高,內部函數已經建立。請看圖片1
    var a = 1
    function fn2() {
        a++ ////代碼執行到這的時候,請看圖片3
        console.log(a)
    }
    return fn2 //代碼執行到這的時候,請看圖片2
}
let fn3 = fn1()
fn3() //代碼執行到這的時候,請看圖片2
fn3 = null //閉包死亡
複製代碼

圖片1面試

圖片2
圖片3

  • 死亡:嵌套在內部函數的對象成爲垃圾對象時就死亡了。

閉包的應用

  • 讓函數外部訪問私有變量 特權方法:有權訪問私有變量(局部的變量、函數的參數或者函數內部定義的其餘函數)和私有方法的方法
function Pay(value) { //Pay是一個閉包
        let money = value
        this.getMoney = function() {
            return money
        }
        this.setMoney = function(value) {
            money = value
        }
}
let pay = new Pay(8000)
console.log(pay.money) //undefined 在外面沒法訪問到私有變量money
console.log(pay.getMoney()) //8000 //getMoney這個特權方法能夠經過對象訪問,getMoney能夠訪問money
pay.setMoney(10000)
console.log(pay.getMoney()) //1000
複製代碼
  • (和上面的相似)定義JS模塊,將全部的數據和功能都封裝在一個函數內部(私有的),只向外暴露一個包含n個方法的對象或者函數。
function myModule() {
     let msg = 'hello world' //私有數據
     function doSomething() { //操做數據的函數
         console.log('dosomething'+msg.toUpperCase())
     }
     function doOtherthing() {
         console.log('doOtherthing'+msg.toLocaleLowerCase())
     }
     return{
        doSomething:doSomething,
        doOtherthing:doOtherthing
     }
}
//模塊的使用者
let module = myModule()
module.doSomething()
module.doOtherthing()
複製代碼
  • 每一個1秒輸出對應li的索引號(利用for循環建立當即執行函數)
for(var i=0;i<lis.length;i++) {
        (function(i) {
            setTimeout(() => {
                alert(i)
            }, 1000);
        })(i)
}
複製代碼
  • 局部做用計數器
function count() {
    let num = 0
    return function() { //函數做爲返回值
        num++
        return num
    }
}
let fn = count()
fn() //1
複製代碼

閉包優勢

  • 延伸了變量的做用範圍和生命週期
  • 避免全局變量的污染

閉包缺點

  • 通常函數的做用域和函數中的變量都會在函數執行結束被垃圾回收機制回收。因爲閉包中的變量在外層函數被調用以後不會被自動清除,因此閉包使用不當容易形成內存泄漏。爲了防止內存泄漏,咱們須要將再也不使用的變量經過手動釋放。能不用閉包就不用。
內存溢出:是一種程序運行出現的錯誤,當出現運行須要的內存超過了剩餘的內存時,就拋出內存溢出的錯誤。
內存泄漏:佔用的內存沒有及時釋放,內存泄漏積累多了就容易致使內存溢出。常見的內存泄漏:閉包、意外的全局變量(函數中沒有用var|let|const聲明的變量)、沒有及時清理的計時器或回調函數。
function demo() {
    let name = 'Eileen'
    console.log('name')
}
demo() //demo函數執行完,demo函數的做用域沒有被外部引用,則該函數的做用域及其變量都會在函數執行完後被銷燬
複製代碼
function foo() {
    let name = 'Eileen'
    return function() {
        name = 'John'
        return name
    }
}
let fn = foo() 
console.log(fn()
fn = null //在全局做用域中執行fn函數,fn函數的環境依賴於foo函數環境,因此foo函數內的做用域及其變量不會被銷燬。fn = null釋放對閉包的引用,foo函數做用域被銷燬
複製代碼
  • 使用閉包以後,能夠訪問到函數內部的私有變量,經過特權方法能夠隨意更改私有變量。

最後再來一道面試題吧!

function fun(n,o) {
    console.log(o)
    return {
        fun: function(m) { 
            return fun(m,n)
        }
    }
}
var a = fun(0) //undefined
    a.fun(1)   //0
    a.fun(2)   //0
    a.fun(3)   //0
var b = fun(0).fun(1).fun(2).fun(3)   //undefined,0,1,2
var c = fun(0).fun(1)   //undefined,0
    c.fun(2)   //1
    c.fun(3)   //1
複製代碼

結語

好啦,以上就是我對閉包的理解和解釋啦,若是有不對的地方,歡迎各位大牛指導一下😁bash

相關文章
相關標籤/搜索