學習JavaScript之閉包

閉包在個人前端學習中一直也是盲點,以前不少次看到別人提到我都是徹底聽不懂。最近一直看書和寫demo,對閉包也逐漸有所理解了,在這裏寫下這篇博客。前端

從做用域鏈講起

首先明確幾個概念:
1.JavaScript有函數級做用域,但沒有塊級做用域。
2.當要使用一個變量時,會沿着做用域鏈一步一步向上查找。
這裏有一個demo:es6

var a = 1

function foo () {
  var a = 2
}

foo()
console.log(a) // a = 1

結果固然是a = 1,雖然在foo函數中從新聲明瞭a而且賦給它一個新的值,可是var聲明的變量只在foo()函數中有效,函數執行完畢就會銷燬,所以全局做用域中a的值沒有變化。
接下來再看這個demo:瀏覽器

for (var i = 0; i < 10; i++) {
  // code
}

console.log(i) // i = 10

這裏在for循環結束以後仍然能在外部訪問到i,就是由於JavaScript沒有塊級做用域形成的。閉包

閉包的特性

閉包的主要特性就是能夠從外部訪問函數內部的屬性和方法。先看一個demo:函數

function foo () {
  var a = 1
}
foo()
console.log(a) // 出錯

正常狀況下,定義在函數內部的局部變量在函數執行完以後就會被銷燬,所以在外部是沒法訪問局部變量的。那應該怎麼作才能訪問呢?請繼續看:性能

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

在這個demo中,咱們在函數foo()內部又定義了一個函數bar(),並把它的函數名返回,這樣便能在外部實現對內部變量a的訪問。學習

有人問了:不是說函數執行結束以後內部變量會被銷燬嗎?你這不科學啊。code

是的,原來函數執行結束以後內部變量的確會被銷燬,可是這裏內部函數bar()在foo()執行時被返回並保存到了外部的baz中。這時候foo()執行完後,baz中依舊保存着對函數bar()的引用,所以bar()的做用域並無被釋放,根據以前提到的變量查找方式,在bar()函數的外層做用域中找到了a。ip

以上就是使用閉包能從外部訪問內部屬性的原理。內存

閉包的用途

利用閉包強大的特性,最方便的用途就是實現私有變量和私有方法;另外,由於使用閉包會在內存中保存函數做用域,所以也能保存變量的值。

此話怎解?請看下面的demo:

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer () {
    console.log(i)
  }, i * 1000)
} // 6 6 6 6 6

這裏的結果並非咱們預想中的每隔一秒依次輸出12345,而是每隔一秒輸出一個6,爲何會這樣呢?

首先,這個6是變量i最終退出循環時的值,其次var聲明的變量沒有塊級做用域,所以i實際上在全局做用域中都訪問獲得。到這裏不難理解了,setTimeout在循環結束後執行timer()函數時,訪問的i實際上在全局做用域中,此時i=6,所以後面的每一個timer()函數執行訪問的i都是6。那麼問題又來了,怎樣才能讓閉包訪問的i是咱們想要的呢?

其實很簡單,讓i變成局部變量,在循環結束以後銷燬,這樣每一個閉包訪問的就是保存在做用域中的局部變量i,也就是對應的12345。請看下面的demo:

for (var i = 1; i <= 5; i++) {
  (function (j) {
    setTimeout(function timer () {
      console.log(j)
    }, j * 1000)
  })(i)
} // 1 2 3 4 5

這裏用到了當即執行函數(IIFE),可能有些難以理解。那我一步一步解釋:當即執行函數其實就是JavaScript模仿塊級做用域的方法,這裏你能夠把它簡單當作一個函數調用。i就是傳給調用函數的參數,內部匿名函數的形參j接收到i的值開始執行setTimeout。此時j就是函數級做用域中的局部變量,在循環結束後,timer()函數開始執行,由於它是一個閉包,因此能訪問保存在做用域中的變量j,也就輸出了咱們想要的結果。

固然更簡單的方式是用ES6的let來聲明i,這樣也是使得i變成局部變量,其餘關於ES6這裏就很少提,有興趣的能夠自行看let基本用法

閉包致使的問題

1.由於閉包會使得函數中的變量都保存在內存中,如不能及時釋放會對性能形成影響。
2.在IE9如下的瀏覽器會有內存泄漏的問題。(關於這塊我後續會寫文章詳細說明)

本人經驗尚淺,目前對於前端仍在不斷摸索和學習,文章若有錯誤,歡迎各位指正。最後附上本人博客地址和原文連接,但願能向各位多多學習。

lbj的前端之路
原文連接:學習JavaScript之原型鏈

相關文章
相關標籤/搜索