[nodejs 內功心法][作用域與閉包系列四] 閉包

閉包

閉包是js一個非常重要但是理解起來又有一定難度的概念,理解閉包能讓你的js功力得到一個質變。

閉包的概念

當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。

JavaScript中的函數會形成閉包。 閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量。

function foo() {
	var a = "哈嘍"

	function bar() {
		console.log(a) 
	}
	
	return bar
}

var baz = foo()
baz() // 哈嘍
// 這就是閉包的效果!!!!!!

函數 bar() 的詞法作用域能夠訪問 foo() 的內部作用域。這個例子bar()在自己定義的詞法作用域以外的地方被正常執行。這就是閉包的威力。
這個函數在定義時的詞法作用域以外的地方被調用。閉包使得函數可以繼續訪問定義時的詞法作用域。

在 foo() 執行後,通常會期待 foo() 的整個內部作用域都被銷燬,因爲我們知道引擎有垃圾回收器用來釋放不再使用的內存空間。由於看上去 foo() 的內容不會再被使用,所以很自然地會考慮對其進行回收。

而閉包的“神奇”之處正是可以阻止這件事情的發生。事實上內部作用域依然存在,因此沒有被回收。是因爲 bar() 在使用這個內部作用域!

拜 bar() 所聲明的位置所賜,它擁有涵蓋 foo() 內部作用域的閉包,使得該作用域能夠一直存活,以供 bar() 在之後任何時間進行引用。
bar() 依然持有對該作用域的引用,而這個引用就叫作閉包

function foo() {
	var a = 2;

	function baz() {
		console.log(a);
  	}
  	bar(baz)
}

function bar(fn) {
	fn(); // 2  這就是閉包的威力
}

無論通過何種手段將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。

循環中的閉包

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

全部會輸出6, 因爲所有函數共享一個 i 的引用

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

// 1
// 2
// 3
// 4
// 5

我們可以用es6的let使用塊作用域來解決這個問題

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

// 1
// 2
// 3
// 4
// 5

for 循環頭部的 let 聲明還會有一 個特殊的行爲。這個行爲指出變量在循環過程中不止被聲明一次,每次迭代都會聲明。隨 後的每個迭代都會使用上一個迭代結束時的值來初始化這個變量。

小結

當函數可以記住並訪問所在的詞法作用域,即使函數是在當前詞法作用域之外執行,這時 就產生了閉包。

發佈了35 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章