JS進擊之路:閉包

引言

閉包這個詞對不少前端開發人員來講既熟悉又陌生,熟悉是由於不少人都用過閉包,可是用的時候不知道閉包,陌生是由於並不理解閉包,接下來這篇文章將會從多方面介紹閉包前端

定義

閉包是怎麼定義的呢?當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數在當前詞法做用域以外執行。來看一個具體例子:面試

function foo () {
  var a = 2
  function bar () {
    console.log(a)
  }
  return bar
}
var baz = foo()
baz() //2

函數bar的詞法做用域能夠訪問foo的內部做用域,而且bar在被做爲返回值賦值給baz執行時,bar函數在定義時的詞法做用域之外的地方被調用,依然能夠訪問foo函數的內部做用域變量a,這就是閉包閉包

分析

如今讓咱們來看爲何閉包能夠在定義的詞法做用域外記住而且訪問定義時的詞法做用域的變量,想要一探究竟,先來看一個簡單的例子來函數的執行過程:函數

function foo (a) {
  console.log(a)
}
foo (a)


上面是一個簡單的函數調用,以及在執行時的上下文環境,重點看執行時上下文環境,在建立foo函數時,會建立一個預先包含全局變量對象的做用域鏈,這個做用域鏈被保存在內部的[[Scope]]屬性中,當調用foo()函數時,會爲函數建立一個執行環境,而後經過複製函數的[[Scope]屬性中的對象構建起執行環境的做用域鏈。此後,又有一個活動對象(包含this、arguments、a)被建立並被推入執行環境做用域鏈的前端,對於foo函數來講,其做用域鏈包含兩個變量對象,一個時全局的變量對象,一個是局部的活動變量對象,通常來講當函數執行完成後,局部的活動變量對象會被銷燬,只留全局的,可是閉包執行過程有所不一樣,來看具體例子:this

function foo () {
  var a = 2
  function bar (b) {
    console.log(a + b)
  }
  return bar
}
var baz = foo()
baz(3) //5


接下來來分析下上面閉包的執行上下環境,在一個函數內部定義的函數會將包含函數的活動對象添加到它的做用域鏈中,所以,bar函數的做用域鏈中會包含foo函數的活動對象,在bar函數從foo中被返回後,它的做用域鏈條被初始化爲全局變量和foo中活動對象,所以,bar函數能夠訪問foo函數中定義的全部變量,同時foo函數在執行完畢後,其活動對象也不會被銷燬,由於bar函數的做用域鏈仍然在引用這個活動對象。spa

常見問題

說到閉包相關的問題,最典型的就是變量和this指向這兩類問題。code

變量

function test () {
  var result = new Array()
  for (var i = 0; i < 6; i++) {
    result[i] = function () {
      return i
    }
  }
  return result
}


上面的代碼展現就是面試題裏面常常會碰到,result的結果從上面截圖能看到,做用域中保存的i都是6,這是爲何呢?由於閉包保存的是函數中的活動對象,所以它們引用的都是同一個變量,而且是變量的最後一個值,所以都是6,那這個問題怎麼解決呢?最多見的最簡單確定是將var換成let,也能夠像下面這樣:對象

function test () {
  var result = new Array()
  for (var i = 0; i < 6; i++) {
    result[i] = (function () {
      return i
    })()
  }
  return result
}

將閉包直接改爲一個自執行函數,自執行函數自己是沒有變量做用域的,所以會使用外層函數的變量做用域,這樣也能達到咱們想要的效果作用域

this指向

var name = "window"
var obj = {
  name: "object",
  getName: function () {
    return function () {
      return this.name
    }
  }
}
console.log(obj.getName()())

上面這段js代碼的this.name的返回值是window,這是爲何呢?按照上面寫到的,此匿名函數在執行過程當中,它的做用域會包含三部分:自身的活動對象、getName函數的活動對象和全局的變量對象,同時每一個活動對象自動取得兩個特殊的變量:this和arguments,可是內部函數在查找this時是沒法直接訪問外部函數的this變量,所以會沿着做用域鏈去查找全局變量中繼續查找,若是想要取外部函數中的this取值也很簡單,只須要向下面代碼這樣:開發

var name = "window"
var obj = {
  name: "object",
  getName: function () {
    var that = this
    return function () {
      return that.name
    }
  }
}
console.log(obj.getName()())

將this賦值給一個變量,內部函數是能夠訪問外部函數變量的,這樣就解決了

總結

閉包是一個容易混淆不清的概念,這篇文章對閉包的定義、執行、常見問題作了簡單的介紹,但願經過這篇能對你們理解和使用閉包有所幫助。若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊。

相關文章
相關標籤/搜索