摘要:原因在于,分配給事件的函數是閉包,他們由函數定義構成,從函數的函數作用域獲取。三個閉包由循環所創建,每個閉包具有同一個詞法環境,環境中包含一個變量當的回調執行時,的值也隨之確定,循環已經執行完畢,對象已經指向了列表的最后一項。
閉包(Closure)
閉包是一個函數和詞法環境的組合,函數聲明在這個詞法環境中詞法作用域
看下面一個例子
function init() { var name = "Mozilla"; // name是局部變量 function displayName() { // displayName()是內部函數,一個閉包 alert(name); // 使用外部函數聲明的變量 } displayName(); } init();
??init()創建了一個局部變量name和一個函數displayName()。函數displayName()是一個已經定義在init()內部的函數,并且只能在函數init()里面才能訪問得到。函數displayName()沒有自己的局部變量,但由于內部函數可以訪問外部函數變量,displayName()可以訪問到聲明在外部函數init()的變量name,如果局部變量還存在的話,displayName()也可以訪問他們。
閉包看下面一個例子
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc();
??運行這段代碼你會發現和之前init()的方法是一樣的效果,但不同之處是,displayName()在執行之前,這個內部方法是從外部方法返回來的。
??首先,代碼還是會正確運行,在一些編程語言當中,一個函數內的局部變量只存在于該函數的執行期間,隨后會被銷毀,一旦makeFunc()函數執行完畢的話,變量名就不能夠被獲取,但是,由于代碼仍然正常執行,這顯然在JS里是不會這樣的。這是因為函數在JS里是以閉包的形式出現的,閉包是一個函數和詞法作環境的組合,詞法環境是函數被聲明的那個作用域,這個執行環境包括了創建閉包時同一創建的任意變量,即創建的這個函數和這些變量處于同一個作用域當中。在這個例子當中,myFunc()是displayName()的函數實例,makeFunc創建的時候,displayName隨之也創建了。displayName的實例可以獲得詞法作用域的引用,在這個詞法作用域當中,存在變量name,對于這一點,當myFunc調用的話,變量name,仍然可以被調用,因此,變量"Mozilla"傳遞給了alert函數。
這里還有一個例子 - 一個makeAdder函數
function makeAdder (x) { return function(y) { return x + y; } } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
??在這個例子當中,我們定義了一個函數makeAdder(x),傳遞一個參數x,并且返回一個函數,這個返回函數接收一個參數y,并返回x和y的和。
??實際上,makeAdder是一個工廠模式 - 它創建了一個函數,這個函數可以計算特定值的和。在上面這個例子當中,我們使用工廠模式來創建新的函數 - 一個與5進行加法運算,一個與10進行加法運算。add5和add10都是閉包,他們共享相同的函數定義,但卻存儲著不同的詞法環境,在add5的詞法環境當中,x為5;在add10的詞法環境當中,x變成了10。
??閉包是很有用的,因為他讓你把一些數據(詞法環境)和一些能夠獲取這些數據的函數聯系起來,這有點和面向對象編程類似,在面向對象編程當中,對象讓我們可以把一些數據(對象的屬性)和一個或多個方法聯系起來
??因此,你能夠像對象的方法一樣隨時使用閉包。實際上,大多數的前端JS代碼都是事件驅動性的 - 我們定義一些事件,當這個事件被用戶所觸發的時候(例如用戶的點擊事件和鍵盤事件),我們的事件通常會帶上一個回調:即事件觸發所執行的函數。例如,假設我們希望在頁面上添加一些按鈕,這些按鈕能夠調整文字的大小,實現這個功能的方式是確定body的字體大小,然后再設置頁面上其他元素(例如標題)的字體大小,我們使用em作為單位。
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; }
??我們設置的調節字體大小的按鈕能夠改變body的font-size,并且這個調節能夠通過相對字體單位,反應到其他元素上,
function makeSizer(size) { return function() { document.body.style.fontSize = size + "px"; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16);
??size12,size14,size16是三個分別把字體大小調整為12,14,16的函數,我們可以把他們綁定在按鈕上。
document.getElementById("size-12").onclick = size12; document.getElementById("size-14").onclick = size14; document.getElementById("size-16").onclick = size16;
12 14 16通過閉包來封裝私有方法
??類似JAVA語言能夠聲明私有方法,意味著只能夠在相同的類里面被調用,JS無法做到這一點,但卻可以通過閉包來封裝私有方法。私有方法不限制代碼:他們提供了管理命名空間的一種強有力方式。
??下面代碼闡述了怎樣使用閉包來定義公有函數,公有函數能夠訪問私有方法和屬性。
var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); console.log(counter.value()); // logs 0 counter.increment(); counter.increment(); console.log(counter.value()); // logs 2 counter.decrement(); console.log(counter.value()); // logs 1
??在先前的例子當中,每個閉包具有他們自己的詞法環境,在這個例子中,我們創建了一個多帶帶的詞法環境,這個詞法環境被3個函數所共享,這三個函數是counter.increment, counter.decrement和counter.value
??共享的詞法環境是由匿名函數創建的,一定義就可以被執行,詞法環境包含兩項:變量privateCounter和函數changeBy,這些私有方法和屬性不能夠被外面訪問到,然而,他們能夠被返回的公共函數訪問到。這三個公有函數就是閉包,共享相同的環境,JS的詞法作用域的好處就是他們可以互相訪問變量privateCounter和changeBy函數
var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } }; var counter1 = makeCounter(); var counter2 = makeCounter(); alert(counter1.value()); /* Alerts 0 */ counter1.increment(); counter1.increment(); alert(counter1.value()); /* Alerts 2 */ counter1.decrement(); alert(counter1.value()); /* Alerts 1 */ alert(counter2.value()); /* Alerts 0 */
??兩個計數器counter1和counter2分別是互相獨立的,每個閉包具有不同版本的privateCounter,每次計數器被調用,詞法環境會改變變量的值,但是一個閉包里變量值的改變并不影響另一個閉包里的變量。
循環中創建閉包:常見錯誤下面一個例子
Helpful notes will appear here
E-mail:
Name:
Age:
function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Your e-mail address"}, {"id": "name", "help": "Your full name"}, {"id": "age", "help": "Your age (you must be over 16)"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp();
??helpText 數組定義了三個有用的hint,每個分別與輸入框的id相對應,每個方法與onfocus事件綁定起來。當你運行這段代碼的時候,不會像預期的那樣工作,不管你聚焦在哪個輸入框,始終顯示你的age信息。
??原因在于,分配給onfocus事件的函數是閉包,他們由函數定義構成,從setupHelp函數的函數作用域獲取。三個閉包由循環所創建,每個閉包具有同一個詞法環境,環境中包含一個變量item.help,當onfocus的回調執行時,item.help的值也隨之確定,循環已經執行完畢,item對象已經指向了helpText列表的最后一項。解決這個問題的方法是使用更多的閉包,具體點就是提前使用一個封裝好的函數:
function showHelp(help) { document.getElementById("help").innerHTML = help; } function makeHelpCallback(help) { return function() { showHelp(help); }; } function setupHelp() { var helpText = [ {"id": "email", "help": "Your e-mail address"}, {"id": "name", "help": "Your full name"}, {"id": "age", "help": "Your age (you must be over 16)"} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeHelpCallback(item.help); } } setupHelp();
??上面代碼運行正常,回調此時不共享一個詞法環境,makeHelpCallback函數給每個回調創造了一個詞法環境,詞法環境中的help指helpText數組中對應的字符串,使用匿名閉包來重寫的例子如下:
function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Your e-mail address"}, {"id": "name", "help": "Your full name"}, {"id": "age", "help": "Your age (you must be over 16)"} ]; for (var i = 0; i < helpText.length; i++) { (function() { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } })(); // Immediate event listener attachment with the current value of item (preserved until iteration). } } setupHelp();
如果你不想使用閉包,你可以使用ES6的let關鍵字
function showHelp(help) { document.getElementById("help").innerHTML = help; } function setupHelp() { var helpText = [ {"id": "email", "help": "Your e-mail address"}, {"id": "name", "help": "Your full name"}, {"id": "age", "help": "Your age (you must be over 16)"} ]; for (var i = 0; i < helpText.length; i++) { let item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp();
??這個例子使用let代替var,所以,每個閉包綁定了塊級作用域,也就意味著不需要額外的閉包
性能考慮??如果閉包在實際案例中是不被允許的,在一個函數中就不一定再創建一個函數,因為這會影響腳本的性能,例如處理的速度和內存的消耗。例如,當創建一個對象,對象的方法應該跟對象的原型聯系起來而不是在對象的構造器里定義,這是因為無論什么時候構造器被調用,方法都會被重新分配
下面一個例子
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }
前面的代碼沒有充分利用閉包,我們重寫如下
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } };
??然而,我們不建議重新定義原型,下面的例子中,給原型分別定義方法而不是重新定義整個原型,這樣會改變constructor的指向。
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };
??在前面兩個例子中,繼承原型可以被所有對象所共享并且在每個對象創建的同時都不必定義方法。
參考MDN closure
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/52000.html
摘要:原因在于,分配給事件的函數是閉包,他們由函數定義構成,從函數的函數作用域獲取。三個閉包由循環所創建,每個閉包具有同一個詞法環境,環境中包含一個變量當的回調執行時,的值也隨之確定,循環已經執行完畢,對象已經指向了列表的最后一項。 閉包(Closure) 閉包是一個函數和詞法環境的組合,函數聲明在這個詞法環境中 詞法作用域 看下面一個例子 function init() { var n...
摘要:原因在于,分配給事件的函數是閉包,他們由函數定義構成,從函數的函數作用域獲取。三個閉包由循環所創建,每個閉包具有同一個詞法環境,環境中包含一個變量當的回調執行時,的值也隨之確定,循環已經執行完畢,對象已經指向了列表的最后一項。 閉包(Closure) 閉包是一個函數和詞法環境的組合,函數聲明在這個詞法環境中 詞法作用域 看下面一個例子 function init() { var n...
摘要:使用上一篇文章的例子來說明下自由變量進階期深入淺出圖解作用域鏈和閉包訪問外部的今天是今天是其中既不是參數,也不是局部變量,所以是自由變量。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第7天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計...
摘要:當在中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了和的當前值的值為因為循環已經結束,的值為。最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。 閉包不是魔法 這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實只要理解了核心概念,閉包并不是那么的難于理解。但是,網上充斥了太多學術性的文章,對于...
閱讀 2137·2021-11-22 15:22
閱讀 1286·2021-11-11 16:54
閱讀 1807·2021-09-23 11:32
閱讀 3007·2021-09-22 10:02
閱讀 1771·2019-08-30 12:59
閱讀 1085·2019-08-29 16:27
閱讀 622·2019-08-29 13:21
閱讀 2464·2019-08-28 17:57