轉自 https://segmentfault.com/a/1190000003818163
1.正確的說,應該是指一個閉包域,每當聲明瞭一個函數,它就產生了一個閉包域(能夠解釋爲每一個函數都有本身的函數棧),每一個閉包域(Function 對象)都有一個 function scope(不是屬性),function scope內默認有個名爲Global的全局引用(有了這個引用,就能夠直接調用 Global 的屬性或方法)javascript
2.凡是在閉包域內聲明的變量或方法,外部沒法直接訪問html
3.閉包域能夠訪問外部的變量或方法
(上圖爲 chrome 下 debug 環境)java
當在一個閉包域內包含另外一個閉包域時(簡單的說就是在一個函數內有另外一個函數,固然這個內部函數的生命週期是依附於外部函數的), 此時,若子閉包域(內部的閉包域,內部函數)使用了父閉包域(外部閉包域,外部函數)的私有變量(在父閉包域中聲明的變量,父閉包域的外部空間沒法直接訪問,但子閉包域能夠訪問),子閉包域即當前的子函數的 function scope 會產生一個 closure 對象屬性,這個對象屬性內包含的是子閉包域對父閉包域的全部引用(只要子閉包域(內部函數)還存活,其父閉包域(外部函數)就依舊存活),假若在父閉包域存活期間對其私有變量內容進行修改,則對這些父閉包域的私有變量進行引用的子閉包域中 function scope 的 closure 對象屬性的內容也會發生變化,由於這只是引用.web
舉例:面試
<!DOCTYPE html>
chrome
<html lang="en">
segmentfault
<head>
數組
<meta charset="UTF-8">
瀏覽器
<title></title>
閉包
</head>
<body>
<script type="text/javascript" charset="utf-8">
//函數 a 有一個私有變量 p 和一個內部函數 innerA
function a() { //外部閉包域 ,一個名爲 a 的 Function 對象
var p = 0; //私有變量 p
var innerA = function () { //內部閉包域 ,一個名爲 innerA 的 Function 對象
console.log(p); //對外部閉包域的私有變量進行了引用,故 innerA 對象的 function scope 會產生一個名爲 closure 的對象屬性,closure 對象內含有一個名爲 p 的引用
}
innerA();//輸出0
p++;
innerA();//輸出1
}
a();
</script>
</body>
</html>
結果以下:
第一次調用innerA
第二次調用 innerA
控制檯輸出
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
//面試經典問題:
function onMyLoad(){
/*
拋出問題:
此題的目的是想每次點擊對應目標時彈出對應的數字下標 0~4,但實際是不管點擊哪一個目標都會彈出數字5
問題所在:
arr 中的每一項的 onclick 均爲一個函數實例(Function 對象),這個函數實例也產生了一個閉包域,
這個閉包域引用了外部閉包域的變量,其 function scope 的 closure 對象有個名爲 i 的引用,
外部閉包域的私有變量內容發生變化,內部閉包域獲得的值天然會發生改變
*/
var arr = document.getElementsByTagName("p");
for(var i = 0; i < arr.length;i++){
arr[i].onclick = function(){
alert(i);
}
}
}
</script>
</head>
<body onload="onMyLoad()">
<p>產品一</p>
<p>產品二</p>
<p>產品三</p>
<p>產品四</p>
<p>產品五</p>
</body>
</html>
解決辦法:
解決辦法一
/*
解決思路:
增長若干個對應的閉包域空間(這裏採用的是匿名函數),專門用來存儲原先須要引用的內容(下標),不過只限於基本類型(基本類型值傳遞,對象類型引用傳遞)
*/
for(var i = 0;i<arr.length;i++){
//聲明一個匿名函數,若傳進來的是基本類型則爲值傳遞,故不會對實參產生影響,
//該函數對象有一個本地私有變量arg(形參) ,該函數的 function scope 的 closure 對象屬性有兩個引用,一個是 arr,一個是 i
//儘管引用 i 的值隨外部改變 ,但本地私有變量(形參) arg 不會受影響,其值在一開始被調用的時候就決定了.
(function (arg) {
arr[i].onclick = function () { //onclick函數實例的 function scope 的 closure 對象屬性有一個引用 arg,
alert(arg); //只要 外部空間的 arg 不變,這裏的引用值固然不會改變
}
})(i); //馬上執行該匿名函數,傳遞下標 i(實參)
}
解決辦法二
/*
解決思路:
將下標做爲對象屬性(name:"i",value:i的值)添加到每一個數組項(p對象)中
*/
for(var i = 0;i<arr.length;i++){
//爲當前數組項即當前 p 對象添加一個名爲 i 的屬性,值爲循環體的 i 變量的值,
//此時當前 p 對象的 i 屬性並非對循環體的 i 變量的引用,而是一個獨立p 對象的屬性,屬性值在聲明的時候就肯定了
//(基本類型的值都是存在棧中的,當有一個基本類型變量聲明其等於另外一個基本變量時,此時並非兩個基本類型變量都指向一個值,而是各自有各自的值,但值是相等的)
arr[i].i = i;
arr[i].onclick = function () {
alert(this.i);
}
}
解決辦法三
/*
解決思路:
與解決辦法一有點類似但卻有點不太類似.
類似點:一樣是增長若干個對應的閉包域空間用來存儲下標
不一樣點:解決辦法一是在新增的匿名閉包空間內完成事件的綁定,而此例是將事件綁定在新增的匿名函數返回的函數上
此時綁定的函數中的 function scope 中的 closure 對象的 引用 arg 是指向將其返回的匿名函數的私有變量 arg
*/
for(var i = 0; i<arr.length;i++){
arr[i].onclick = (function(arg){
return function () {
alert(arg);
}
})(i);
}
解決辦法四
/*
解決思路與解決辦法一相同
*/
for(var i = 0; i<arr.length;i++){
(function(){
var temp = i;
arr[i].onclick = function () {
alert(temp);
}
})();
}
解決辦法五
/*
解決思路與解決辦法三及四相同
*/
for(var i = 0;i<arr.length;i++){
arr[i].onclick = (function () {
var temp = i;
return function () {
alert(temp);
}
})();
}
解決辦法六
/*
解決思路:
將下標添加爲綁定函數的屬性
*/
for(var i = 0;i<arr.length;i++){
(arr[i].onclick = function () {
alert(arguments.callee.i); //arguments 參數對象 arguments.callee 參數對象所屬函數
}).i = i;
}
解決辦法七
/*
解決思路:
經過 new 使用 Function 的構造函數 建立 Function 實例實現,因爲傳入的函數體的內容是字符串,故 Function 獲得的是一個字符串拷貝,而沒有獲得 i 的引用(這裏是先獲取 i.toString()而後與先後字符串拼接成一個新的字符串,Function 對其進行反向解析成 JS 代碼)
*/
for(var i = 0;i<arr.length;i++){
arr[i].onclick = new Function("alert("+i+");");//每 new 一個 Function 獲得一個 Function 對象(一個函數),有本身的閉包域
}
解決辦法八
/*
解決思路:
直接經過 Function 返回一個函數
與解決辦法七的不一樣之處在於:
解決辦法七使用 new,使用了 new,此時 Function 函數就被當成構造器能夠用來構造一個 Function 實例返回
當前解決辦法沒有使用 new ,即將 Function 函數當成一個函數,傳入參數返回一個新函數;
其實此處 new 與不 new 只是的區別在於:
使用了 new 即 Function 函數充當構造器,由 JS 解析器生產一個新的對象,構造器內的 this 指向該新對象;
不實用 new 即 Function 函數依舊是函數,由函數內部本身生產一個實例返回.
*/
for(var i = 0;i<arr.length;i++){
arr[i].onclick = Function("alert("+i+");");
}
解決辦法九
使用ES6新語法 let 關鍵字 因爲幾新東西 各瀏覽器支持不一樣
chrome 及 opera支持如下語法
<script type="application/javascript">
"use strict";//使用嚴格模式,不然報錯 SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
var arr = document.getElementsByTagName("p");
for(var i = 0;i<arr.length;i++){
let j = i;//建立一個塊級變量
arr[i].onclick = function () {
alert(j);
}
}
</script>
在 chrome 查看
能夠在控制檯看到 j 變量是一個 block 級的變量
待函數綁定完成後看數組項:
此時的該數組項的<function scope>的 Block 域有個 j 存儲的就是對應的數組下標
firefox支持一下語法
<script type="application/javascript;version=1.7">
var arr = document.getElementsByTagName("p");
for(var i = 0;i<arr.length;i++){
let j = i;
arr[i].onclick = function () {
alert(j);
}
}
</script>
因爲新語法各大廠商的支持還沒有規範故暫不不推薦使用
解決辦法大同小異,只要理解其中的實質,能夠寫出多多的解決辦法
點擊閱讀全文,查看更多