有很多開發人員老是搞不清匿名函數和閉包兩個概念,所以常常混用。閉包是指有權訪問另外一個函數做用域中的變量的函數。建立閉包的常見方式,就是在一個函數內部建立另外一個函數。 ——————摘自《JavaScript高級程序設計》javascript
上面說的很清楚,就是一個函數內部再建立另外一個函數(尷尬。。。好像跟書上說的同樣。多餘解釋)。我以前面試也常常被問到這個問題。。。沒想到書上就這麼一句。如下須要有一點js的基礎,瞭解做用域才能看明白。。。前端
咱們先來看一個最簡單的⬇️java
function a(){
var b = 10;
return function() {
return b
}
}
console.log(a()()); // 10
複製代碼
看,奇蹟吧!!!咱們輸出了a函數中的局部變量b的值。就是這麼神奇。。。
誒??大家可能會說那我這個局部變量b,至關於一個常量,若是我像下面⬇️這樣豈不是一個效果?面試
function a() {
var b = 10;
return b
}
console.log(a());
複製代碼
好的,沒有錯,那咱們再換種方式⬇️編程
function a(paramA) {
return function b(paramB) {
return paramA + paramB
}
}
var variable10 = a(10);
var variable20 = a(20);
console.log(variable10(5)); // 15
console.log(variable20(5)); // 25
複製代碼
上面這個例子,在咱們執行輸出variable10(5)
和variable20(5)
的時候,爲何咱們能獲得5與以前執行a(10)
時傳進去的10呢?這就是咱們閉包的用處,能夠獲取到函數內的局部變量,並相加。
等等!也許有人不明白了,怎麼是局部變量了呢?那我按下面這種寫法可能清楚一些⬇️閉包
function a(paramA) {
+ var variableA = paramA;
return function b(paramB) {
return variableA + paramB
}
}
var variable10 = a(10);
/** * a(10)是傳進去是什麼樣的呢? * * function a(10) { * var variableA = 10; * return function b(paramB) { * return variableA + paramB * } * } * 上面語法不對哦,我只是寫的好理解一些 * 也就是說variable10指向了內部函數b * var variable10 = function b(paramB) { * return 10 + paramB * } */
var variable20 = a(20);
console.log(variable10(5)); // 15
console.log(variable20(5)); // 20
複製代碼
經過上面的註釋,想必你們應該已經瞭解閉包是什麼了,就是在一個外部函數內部建立另外一個函數,以致於全局調用內部函數的時候能夠訪問到外部函數的局部變量,並使用。函數
來,上《JavaScript高級程序設計》的例子ui
function createComparisonFunction(propertyName) {
return function (object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2) {
return -1
} else if (value1 > value2) {
return 1
} else {
return 0;
}
}
}
複製代碼
在這個例子中,var value1
和var value2
這兩行代碼訪問了外部函數中的變量propertyName
,即便這個內部函數被返回了,並且是在其餘地方被調了,但它仍然能夠訪問變量propertyName
;這就相似於我上面的例子(代碼接上)this
var manA = { name: '江戶川柯南', age: 7 };
var manB = { name: '工藤新一', age: 17 };
var whoOlder = createComparisonFunction('age');
// 返回-1爲前者小於後者,返回1位前者大於後者,返回0二者同樣大
console.log(whoOlder(manA, manB)); // -1,柯南小於新一
複製代碼
上面的例子就至關於開始介紹閉包時的例子,不明白?就是spa
var whoOlder = createComparisonFunction('age');
/** * var whoOlder = createComparisonFunction('age') { * var propertyName = 'age'; * return function (object1, object2) { * var value1 = object1['age']; * var value2 = object2['age']; * if (value1 < value2) { * return -1 * } else if (value1 > value2) { * return 1 * } else { * return 0; * } * } * } * 上面語法不對啊,我只是寫的好理解一些 * 最後獲得的就是 * var whoOlder = function (object1, object2) { * var value1 = object1['age']; * var value2 = object2['age']; * if (value1 < value2) { * return -1 * } else if (value1 > value2) { * return 1 * } else { * return 0; * } * } * } */
複製代碼
重點!!! 在一個函數被調用的時候,會建立一個執行環境(execution context)及相應的做用域鏈。而後,還有初始化一個活動對象(activation object),這個活動對象裏有什麼呢?有arguments和其餘命名參數的值,就是⬇️
function a(paramA, paramB) {
return paramA + paramB
}
a(1, 2)
/** * 這個函數的活動對象有什麼呢??有三個 * arguments [1, 2] * paramA 1 * paramB 2 */
複製代碼
而後就是剛說的做用域鏈,外部函數的活動對象會始終處於第二位,就是好比我在內部函數用到變量a,會先在內部函數找有沒有聲明a這個變量,若是沒有,就會去外部函數找,外部函數沒有就會往全局執行環境找。
在函數執行過程當中,爲讀取和寫入變量的值,就須要在做用域鏈中查找變量,來看下面的例子(《JavaScript高級程序設計》例子)
function compare(value1, value2) {
if (value1 < value2) {
return -1
} else if (value1 > value2) {
return 1
} else {
return 0
}
}
var result = compare(5, 10)
複製代碼
這個的做用域鏈畫出來就是
想必你們面試的時候都會遇到一個很是很是經典的題,題面就是:
頁面上有十個
<li></li>
標籤,就是一個列表有十項,須要點擊每一個li輸出對應的索引。
經典錯誤答案:
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i ++) {
list[i].addEventListener('click', function () {
console.log(i)
})
}
複製代碼
這咱們如今都知道是輸出同一個數了,這是爲何呢?我來換一種寫法:
var list = document.getElementsByTagName('li');
var i = 0;
for (i; i < list.length; i ++) {
list[i].addEventListener('click', function () {
console.log(i)
})
}
複製代碼
這下咱們能夠看清楚了吧,當這段代碼都執行完,尚未點擊li的時候,全局變量i已經變成最後一個數了,假設有十個li,i就已經根據循環變成了10。因此,當咱們點擊li的輸入全局變量i的時候,固然每次輸出的都是10了。
經典正確答案:
var list = document.getElementsByTagName('li');
for (var i = 0;; i < list.length; i ++) {
(function (i) {
list[i].addEventListener('click', function () {
console.log(i)
})
})(i)
}
複製代碼
在for循環裏,每執行一次,執行一個自調函數,並把i當作參數傳進去,js函數的參數有個按值傳遞的特性,也能夠理解爲咱們前面講過的活動對象,這個自調函數的活動對象裏面有個i的值,因此這時候每一個li上要執行的監聽函數輸出的就不在是全局變量i,而是活動對象中的i,由於循環的時候沒循環一次執行一次自調函數,因此每一個自調函數之間是各自獨立的,因此輸出的i值也天然不同了。
固然也有人會說用ES6的語法更簡單一些:
var list = document.getElementsByTagName('li');
- for (var i = 0; i < list.length; i ++) {
+ for (let i = 0; i < list.length; i ++) {
list[i].addEventListener('click', function () {
console.log(i)
})
}
複製代碼
就是簡單的把聲明i的var改爲let,這是爲何呢,這是由於ES6中新的規定let聲明的是自帶做用域的變量,而且ES6有塊級做用域的概念,因此會把每次循環的i值鎖死,天然輸出的就是不同的值啦;
閉包就先寫到這了,若是有哪裏寫的不對的地方但願你們指正,我立馬更改,我也不想誤人子弟。寫這篇博客也是爲了讓本身對閉包這個概念有個從新的認識,對活動對象,做用域鏈更清楚了,可是還有好多東西沒有寫,好比this指向問題,內存泄漏怎麼處理,ES5是怎麼模仿塊級做用域的,私有變量,私有函數,模塊模式,這都是一個閉包就能夠延伸出來的問題,這在《JavaScript高級程序設計》第7章第4節均可以找到,靜下心來看一看,我相信仍是能夠看明白的,一開始看不懂,就一段時間看一遍,每次確定會有不一樣的理解。
對於前端來講,追求時髦的技術當然沒錯,畢竟時髦的技術有着先進的思想,他能告訴你應該怎樣編程。可是在編程以前,仍是須要學好基礎,基礎弄明白了,確定會對js,前端,有一個全新的認識。
我是前端戰五渣,一個前端界的小學生。