相關係列: 從零開始的前端築基之旅(面試必備,持續更新~)javascript
在理解閉包以前,有個重要的概念須要先了解一下,就是 js 執行上下文。前端
每當引擎遇到一個函數調用,它會爲該函數建立一個新的執行上下文並壓入棧的頂部。java
當該函數執行結束時,執行上下文從棧中彈出,控制流程到達當前棧中的下一個上下文。執行上下文中聲明的全部變量都將被刪除。面試
先看個栗子:閉包
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope(); // local scope
複製代碼
依據詞法做用域邏輯,查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象。函數
所以,內部函數f在執行時,找到定義位置的父級函數checkscope內的scope並將其返回,而後內部函數與外部函數的上下文依次彈出並銷燬。post
再看另外一個栗子:ui
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
const f2 = checkscope();
f2(); // local scope
複製代碼
與上個栗子不一樣,這裏並無在函數內部調用f,而是將f返回賦值給f2,所以,隨着checkscope函數的結束,由於f2持有對函數f的引用,f沒有被銷燬,又由於f持有外部函數的做用域,因此scope也沒有被銷燬。spa
由此得出結論:.net
{}
塊裏面定義一個函數來建立閉包。閉包能夠訪問外部做用域,即便這個外部做用域已經執行結束。閉包只存儲外部變量的引用,而不會拷貝這些外部變量的值。
function initEvents(){
for(var i=1; i<=3; i++){
setTimeout(function showNumber(){
console.log(i)
},10);
}
}
initEvents(); // 4,4,4
複製代碼
這個示例中,咱們建立了3個閉包,皆引用了同一個變量 i
,因爲變量 i
隨着循環自增,所以最終輸出的都是一樣的值。
在 for
語句塊中使用 let
變量聲明,將在每次循環中爲 for
語句塊建立一個新的局部變量。
function initEvents(){
for(let i=1; i<=3; i++){
setTimeout(function showNumber(){
console.log(i)
},10);
}
}
initEvents();
複製代碼
經過閉包,咱們能夠建立擁有私有狀態的函數,閉包使得狀態被封裝起來。
經過閉包,咱們能夠建立自增生成器函數。一樣,內部狀態是私有的。示例以下:
function createAGenerate(count, increment) {
return function(){
count += increment;
return count;
}
}
let generateNextNumber = createAGenerate(0, 1);
console.log(generateNextNumber()); //1
console.log(generateNextNumber()); //2
console.log(generateNextNumber()); //3
let generateMultipleOfTen = createAGenerate(0, 10);
console.log(generateMultipleOfTen()); //10
console.log(generateMultipleOfTen()); //20
console.log(generateMultipleOfTen()); //30
複製代碼
以上示例中,咱們能夠建立一個擁有私有狀態的函數。同時,咱們也能夠建立多個擁有同一私有狀態的函數。基於此,咱們還能夠建立一個擁有私有狀態的對象。
function TodoStore(){
let todos = [];
function add(todo){
todos.push(todo);
}
function get(){
return todos.map(toTodoViewModel);
}
function toTodoViewModel(todo) {
return { id : todo.id, title : todo.title };
}
return Object.freeze({
add,
get
});
}
複製代碼
複製代碼
TodoStore()
函數返回了一個擁有私有狀態的對象。在外部,咱們沒法訪問私有的 todos 變量,而且 add 和 get 這兩個閉包擁有相同的私有狀態。在這裏,TodoStore()
是一個工廠函數。
閉包就是那些引用了外部做用域中變量的函數。
爲了更好的理解,咱們將內部函數拆成閉包和純函數兩個方面:
在上述例子中,add()
和 get()
函數是閉包,而 isPriorityTodo()
和 toTodoViewModel()
則是純函數。
在 Javascript
中,局部變量會隨着函數的執行完畢而被銷燬,除非還有指向他們的引用。當閉包自己也被垃圾回收以後,這些閉包中的私有狀態隨後也會被垃圾回收。一般咱們能夠經過切斷閉包的引用來達到這一目的。
在 Javascript
中,咱們很容易建立出全局變量。任何定義在函數和 {}
塊以外的變量都是全局的,定義在全局做用域中的函數也是全局的。
若是你收穫了新知識,請給做者點個贊吧,在左側邊欄第一個按鈕點一下~
參考文章: