閉包是何時被建立的,何時被銷燬的?具體的實現又是怎麼樣的?javascript
一開始學閉包的時候,囫圇吞棗😔,但願此次能夠靜下來好好琢磨琢磨,對閉包有更深的理解。html
🤭爲了更好的理解閉包,有必要先了解做用域跟執行上下文相關的內容:java
話很少說,上來就是一個思考題🤔異步
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()());
複製代碼
👆固然了js基礎紮實的,必定明白裏面的端詳,看完一下內容,對於這道題必定就能遊刃有餘的解決了✊函數
來看看紅寶石怎麼說的👇post
閉包是指有權訪問另一個函數做用域中的變量的函數
複製代碼
先來看看MDN定義👇ui
函數和對其周圍狀態(**lexical environment,詞法環境**)的引用捆綁在一塊兒構成**閉包**(**closure**)。也就是說,閉包可讓你從內部函數訪問外部函數做用域。在 JavaScript 中,每當函數被建立,就會在函數生成時生成閉包。
複製代碼
👆上面定義大概就是:閉包是指那些可以訪問自由變量的函數。其中自由變量,指在函數中使用的,但既不是函數參數arguments也不是函數的局部變量的變量,其實就是另一個函數做用域中的變量。this
網上對於閉包的定義各類各樣,每一個人對閉包的理解不一樣,天然定義就不一樣,那麼我也不去定義閉包,本身意會吧👊
複製代碼
非要理解的話,有兩種理解:
1️⃣閉包是嵌套的內部函數(絕大數人)
2️⃣包含被引用變量(或函數)的對象(極少數人)
我以爲閉包存在與嵌套的內部函數中😊
咱們針對第二個理解,寫一個demo👇
function count () {
var x = 0
return {
add() {
x++;
},
print() {
console.log(x)
}
}
}
let demo = count();
demo.add()
demo.print()
demo.add()
demo.print()
複製代碼
嗯🙃 只可意會不可言傳,往下再看看吧!
首先要明白做用域鏈的概念,其實也很簡單,在ES5中只存在兩種做用域
1️⃣全局做用域 2️⃣局部做用域
當訪問一個變量時,解釋器會首先在當前做用域查找標示符,若是沒有找到,就去父做用域找,直到找到該變量的標示符或者不在父做用域中,這就是做用域鏈。 值得注意的是,每個子函數都會拷貝上級的做用域,造成一個做用域的鏈條。 好比:
var x = 1;
function demo() {
var x = 2
function demo2() {
var x = 3;
console.log(x);//3
}
}
複製代碼
上面這段代碼中,我我的的理解demo函數做用域指向全局做用域(window)和它自己,而demo2函數的做用域指向全局做用域(window)丶demo 和它自己。而且的話,做用域的查找是從最底層開始向上找的,直到找到全局做用域window爲止,若是全局做用域尚未找到的話,就會報錯❌
閉包產生的本質💯 當前環境中存在指向父級做用域的引用。仍是舉上面的例子:👇
var x = 1;
function demo() {
var x = 2
function demo2() {
console.log(x);
}
return demo2;
}
var h = demo();
h() // 2
複製代碼
這裏h變量會拿到父級做用域的變量,輸出2。在當前的環境中,含有對demo2的引用,demo2偏偏引用了window demo 和 自己的做用域。 所以demo2能夠訪問到demo做用域中的變量。
問題來了? **是否是隻有返回函數纔會產生閉包呢?**❓
回到閉包實質:只須要讓父級做用域的引用存在便可 那麼咱們能夠這麼作:point_down:
var demo2;
function demo() {
var x = 2
demo2 = function () {
console.log(x);
}
}
demo();
demo2() // 2
複製代碼
首先讓外部函數執行,給demo2賦值,等於demo2如今擁有了window demo 和 自身的做用域的訪問權限,那麼查找變量x的時候,會逐級的向上去找,在最近的demo做用域找到標示符就返回結果,輸出2。
🤭 在這裏外部的變量demo2存在父級做用域的引用,所以產生了閉包,形式變了,本質仍是沒有改變。
明白了本質,咱們從真實的場景中出發,究竟在哪些地方能體現閉包的存在❓
1️⃣返回一個函數,上面已經舉例子了
:two:做爲函數參數傳遞
var a = 1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
// 這就是閉包
fn();
}
// 輸出2,而不是1
foo();
複製代碼
:three:在定時器、事件監聽、Ajax請求、跨窗口通訊、Web Workers或者任何異步中,只要使用了回調函數,實際上就是在使用閉包
如下的閉包保存的僅僅是window和當前做用域。
// 定時器
setTimeout(function timeHandler(){
console.log('2222');
},100)
// 事件監聽
$('#div').click(function(){
console.log('DIV Click');
})
複製代碼
:four:IIFE(當即執行函數表達式)建立閉包, 保存了全局做用域window
和當前函數的做用域
,所以能夠全局的變量。
var x = 22;
(function IIFE(){
// 輸出22
console.log(x);
})();
複製代碼
1️⃣函數嵌套
:two:內部函數引用了外部函數的數據(變量/函數)
說到閉包的銷燬,得先聊一聊的就是V8的垃圾回收機制
V8垃圾回收機制,我想到時候我會開一個新的章節去接受它🤭