參考博客:
前端基礎進階(三):變量對象詳解
js閉包其實不難,你須要的只是瞭解什麼時候使用它前端
代碼在被調用時,會建立一個執行上下文,執行上下文能夠理解爲當前代碼的執行環境,它會造成一個做用域,咱們通常討論全局做用域和函數做用域兩種狀況。
執行上下文有生命週期,分別爲上下文建立階段,在該階段會肯定變量對象、做用域鏈和this指向,建立結束後會進入執行階段,完成相關賦值與調用。緩存
執行上下文在建立階段吧會產生變量對象,變量對象存儲了當前做用域中的變量參數和函數,當建立完成後,代碼開始執行,變量被賦予相應的值,變量對象就被激活爲活動變量。 變量對象的建立,依次經歷瞭如下幾個過程。bash
第3條的含義是,首先,函數聲明的優先級要比變量高,會聲明提早到變量前面,對於函數與變量同名的狀況,同名的變量就不會被賦值undefined。固然這條只適用於建立階段階段,當代碼開始執行時,賦值就按照前後順序來。閉包
// 不一樣名狀況
console.log(a) // f a(){}
console.log(b) // undefined
function a(){}
var b=10
console.log(b) // 10
// 上述代碼的編譯執行順序爲:
(1) 函數聲明提早
function a(){}
(2) 變量聲明提早並賦值undefined
var b = undefined
(3) 編譯完成執行代碼
function a(){}
var b=undefined
console.log(a) // f a(){}
console.log(b) // undefined
b=10 // 執行時被賦值10
console.log(b) // 10
// 同名狀況
console.log(a) // f a(){}
function a(){
}
var a=10
console.log(a) // 10
// 這段代碼編譯執行的順序爲:
(1) 函數聲明提早
function a(){}
(2) 變量由於同名,因此跳過賦值undefined
(3) 編譯完成,執行代碼
function a(){}
console.log(a) // f a(){}
a=10 // 執行時被賦值10
console.log(a) // 10
複製代碼
首先要明確的是,做用域在代碼定義時就產生了,不管該段代碼在哪裏調用,做用域都不會改變。那代碼什麼時候定義?我理解的是當所處的環境被調用時,如一個函數嵌套一個函數,只有當外部函數被調用時,內部函數才被定義。
由於函數存在嵌套關係,會造成一個做用域鏈,做用域鏈是由當前環境與上層環境的一系列變量對象組成,它保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。app
var a = 10;
var fn = null;
function outer() { // 做用域鏈爲 本身->全局
console.log(a) // 10
console.log(b) // undefined
var b = 20;
console.log(b) // 20
function inner() {
console.log(b) // 20
console.log(c) // 報錯
}
fn = inner;
}
function other() {
var c = 30;
fn();
}
outer(); // 10 undefined 20
other(); // 20,報錯,找不到c
複製代碼
首先,是全局代碼執行,進入函數調用棧,生成全局執行上下文,分爲兩個階段,首先是建立階段,函數聲明提高,變量提高並賦值undefined,由於outer和other被定義了,因此建立了各自的做用域鏈,分別爲各自的做用域到全局做用域;建立完成後開始執行,變量被賦值,開始調用函數outer。
調用outer時,outer進入函數調用棧,開始生成outer的執行上下文,也分爲兩個階段,建立階段,變量提高,函數定義,產生變量對象放到本身的做用域中,此時inner被定義,建立了本身的做用域鏈,爲 inner的做用域到outer的做用域再到全局做用域;outer的執行上下文建立完成後開始執行相應代碼,依次輸出10 unndefined 20;而後執行結束,彈出函數調用棧。
接着調用other,也是一樣的步驟,注意執行到fn時,由於fn被賦值inner,做用域跟inner同樣,而inner的做用域鏈在定義的時候就肯定了,不管在哪裏執行都不會改變,因此此時inner的做用域鏈也只有本身、outer和全局的,並不能訪問到other中的變量,因此找不到c。函數
其實上述代碼就是閉包的場景,outer雖然調用結束了,可是由於做用域鏈的緣由,並不會被摧毀回收,inner仍然能夠訪問到做用域鏈上的變量。
簡單來講,閉包就是外部函數能夠訪問某個函數內部變量的機制,因此造成閉包的必要條件就是,函數嵌套函數,同時將內部函數返回出來。性能
(1)點贊功能,每一個點贊按鈕點擊加1,同時互不影響ui
function add(){
var count=0;
return function(){
return count++
}
}
// 給按鈕綁定不一樣的點擊事件
for(i in n){
var fn=add()
btn[i].onclick=fn;
}
複製代碼
(2)數據緩存.假若有一個計算乘積的函數,mult函數接收一些number類型的參數,並返回乘積結果。爲了提升函數性能,咱們增長緩存機制,將以前計算過的結果緩存起來,下次遇到一樣的參數,就能夠直接返回結果,而不須要參與運算。這裏,存放緩存結果的變量不須要暴露給外界,而且須要在函數運行結束後,仍然保存,因此能夠採用閉包。this
var mult = (function(){
var cache = {};
var calculate = function() {
var a = 1;
for(var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
return function() {
var args = Array.prototype.join.call(arguments, ',');
if(args in cache) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
}())
複製代碼
感受閉包仍是須要多寫案例才能真正弄得清楚,爲何這麼設計,會持續更新。spa