JavaScript之閉包

本文一共 1300 字,讀完只需 5 分鐘javascript

概述

閉包, 能夠說是每一個前端工程師都據說的一個詞,咋一看很難從字面上去理解,從而給人留下了閉包是一個重要又難以理解的概念。前端

可是,閉包在 JS 代碼能夠說是隨處可見,閉包也只是計算機領域的一個概念而已,它的存在是由於 JS 的一些語言特性,好比:函數式語言執行上下文執行上下文棧,做用域鏈詞法做用域java

執行上下文:Execution Context
執行上下文棧:Execution Context Stack
做用域鏈:Scope Chain
做用域:Scopebash

本篇文章,將先給結論,到底什麼是閉包,再來分析產生閉包的過程和緣由。前端工程師

1、什麼是閉包

當函數記住並訪問所在詞法做用域的自由變量時,就產生了閉包,即便函數是在當前詞法做用域外執行。 --《你不知道的 JavaScript》閉包

來段經典的閉包代碼:app

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
複製代碼

內部函數 inner 記住了它被定義時的詞法做用域,也就是 outter 的函數做用域,並訪問了該做用域裏的自由變量 a, 同時,inner 函數做用返回值,在外部做用域中被執行。函數

以上描述,所有符合閉包的描述,那這就是閉包post

2、執行過程

以前的文章講了函數的執行上下文棧,變量對象,做用域鏈等內容,接下來經過閉包代碼回顧代碼是怎麼樣的執行過程。ui

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
複製代碼
  1. 進入全局代碼的執行上下文,全局上下文被壓入執行上下文棧。
ECStack = [
        globalContext
    ];
複製代碼
  1. 全局上下文建立全局變量對象,建立 this 並指向全局上下文。
globalContext = {
    VO: global,
    scope: [global.VO],
    this: global
}
複製代碼
  1. 全局上下文初始化時,outter 函數被建立,創建做用域鏈,複製 Scope 屬性到 outter 函數的內部屬性[[scope]]
outter.[[scope]] = [     
    globalContext.VO
  ];
複製代碼
  1. 執行 outter 函數,建立 outter 函數執行上下文,將 outter 上下文壓入執行上下文棧。
ECStack = [
        globalContext,
        outterContext
    ];
複製代碼
  1. 初始化 outter 函數執行上下文,用 arguments 建立活動對象,加入形參、函數聲明、變量聲明。將活動對象壓入 outter 做用域鏈頂端。
outterContext = {
    AO: {
        arguments: {
            a: undefined,
        }
        length: 1
    },
    scope: undefined,
    inner: reference to function inner(){}
    Scope: [AO, globalContext.VO],
    this: undefined
}
複製代碼
  1. outter 執行完畢,接着執行 outter 返回的被變量引用的函數 inner;
ECStack = [
        globalContext,
        innerContext
    ];
複製代碼
  1. inner 函數初始化,過程和第4步同樣。
innerContext = {
        AO: {
            arguments: {
                length: 0
            }
        },
        Scope: [AO, outterContext.AO, globalContext.VO],
        this: undefined
    }
複製代碼
  1. inner 執行,沿着做用域鏈查找變量 a, 打印 a 值。
  2. inner 函數執行結束,彈出執行上下文棧。
ECStack = [
        globalContext
    ];
複製代碼

在這個過程當中,第 5 步,outter 已經執行結束,執行上下文按理來講已經被銷燬,內部函數 inner 怎麼還能訪問 outter 做用域的變量呢。

正是因爲閉包,inner 引用了它所在詞法做用域的自由變量 a,inner 的做用域鏈中仍然是完整的, 儘管 inner 在其餘地方執行,仍是返回了正確結果。

3、函數式語言

閉包中,一個很重要的特色就是,內部函數做爲一個數據被返回。這是因爲 JS 是函數式語言,函數能夠做爲參數傳遞進函數,也能夠做爲一個數據返回。函數的嵌套構成了做用域的嵌套,也就有了做用域鏈。

因爲函數具備做用域,且變量的尋找具備 「遮蔽效應」(從內到外,找到第一個就中止),使得局部做用域的變量對於外部做用域是不可見的,因而函數就有了封閉性,因此咱們拿函數來包裹封裝私有變量,同時也有了閉包。

4、自由變量

自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量。

function outter() {
    var a = 123;
    
    function inner() {
        console.log(a);
    }
    return inner;
}

var foo = outter();
foo();  // 123
複製代碼

對於 inner 函數而言,變量 a, 不是它的函數參數,也不是它的局部變量,a 就是自由變量。

5、閉包的用處和缺點

從閉包的特色能夠看出,自由變量保存在了內存中,並能間接訪問。

那麼閉包的做用就是:

隱藏私有變量,解決變量命名空間污染的問題。

缺點
若是閉包過多,變量常駐內存,確定會佔用大量內存空間。

總結

因爲 JS 是函數式語言,當函數記住並訪問所在詞法做用域的自由變量時,就產生了閉包,即便函數是在當前詞法做用域外執行。

閉包在 JS 代碼中很是常見,沒必要把它想得太玄乎。

歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。

掘金專欄 JavaScript 系列文章

  1. JavaScript之變量及做用域
  2. JavaScript之聲明提高
  3. JavaScript之執行上下文
  4. JavaScript之變量對象
  5. JavaScript原型與原型鏈
  6. JavaScript之做用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中完全理解this
  12. JavaScript專題之模擬實現call和apply
相關文章
相關標籤/搜索