摘要:原因在于,分配給事件的函數(shù)是閉包,他們由函數(shù)定義構(gòu)成,從函數(shù)的函數(shù)作用域獲取。三個(gè)閉包由循環(huán)所創(chuàng)建,每個(gè)閉包具有同一個(gè)詞法環(huán)境,環(huán)境中包含一個(gè)變量當(dāng)?shù)幕卣{(diào)執(zhí)行時(shí),的值也隨之確定,循環(huán)已經(jīng)執(zhí)行完畢,對(duì)象已經(jīng)指向了列表的最后一項(xiàng)。
閉包(Closure)
閉包是一個(gè)函數(shù)和詞法環(huán)境的組合,函數(shù)聲明在這個(gè)詞法環(huán)境中詞法作用域
看下面一個(gè)例子
function init() { var name = "Mozilla"; // name是局部變量 function displayName() { // displayName()是內(nèi)部函數(shù),一個(gè)閉包 alert(name); // 使用外部函數(shù)聲明的變量 } displayName(); } init();
??init()創(chuàng)建了一個(gè)局部變量name和一個(gè)函數(shù)displayName()。函數(shù)displayName()是一個(gè)已經(jīng)定義在init()內(nèi)部的函數(shù),并且只能在函數(shù)init()里面才能訪問(wèn)得到。函數(shù)displayName()沒(méi)有自己的局部變量,但由于內(nèi)部函數(shù)可以訪問(wèn)外部函數(shù)變量,displayName()可以訪問(wèn)到聲明在外部函數(shù)init()的變量name,如果局部變量還存在的話,displayName()也可以訪問(wèn)他們。
閉包看下面一個(gè)例子
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc();
??運(yùn)行這段代碼你會(huì)發(fā)現(xiàn)和之前init()的方法是一樣的效果,但不同之處是,displayName()在執(zhí)行之前,這個(gè)內(nèi)部方法是從外部方法返回來(lái)的。
??首先,代碼還是會(huì)正確運(yùn)行,在一些編程語(yǔ)言當(dāng)中,一個(gè)函數(shù)內(nèi)的局部變量只存在于該函數(shù)的執(zhí)行期間,隨后會(huì)被銷毀,一旦makeFunc()函數(shù)執(zhí)行完畢的話,變量名就不能夠被獲取,但是,由于代碼仍然正常執(zhí)行,這顯然在JS里是不會(huì)這樣的。這是因?yàn)楹瘮?shù)在JS里是以閉包的形式出現(xiàn)的,閉包是一個(gè)函數(shù)和詞法作環(huán)境的組合,詞法環(huán)境是函數(shù)被聲明的那個(gè)作用域,這個(gè)執(zhí)行環(huán)境包括了創(chuàng)建閉包時(shí)同一創(chuàng)建的任意變量,即創(chuàng)建的這個(gè)函數(shù)和這些變量處于同一個(gè)作用域當(dāng)中。在這個(gè)例子當(dāng)中,myFunc()是displayName()的函數(shù)實(shí)例,makeFunc創(chuàng)建的時(shí)候,displayName隨之也創(chuàng)建了。displayName的實(shí)例可以獲得詞法作用域的引用,在這個(gè)詞法作用域當(dāng)中,存在變量name,對(duì)于這一點(diǎn),當(dāng)myFunc調(diào)用的話,變量name,仍然可以被調(diào)用,因此,變量"Mozilla"傳遞給了alert函數(shù)。
這里還有一個(gè)例子 - 一個(gè)makeAdder函數(shù)
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
??在這個(gè)例子當(dāng)中,我們定義了一個(gè)函數(shù)makeAdder(x),傳遞一個(gè)參數(shù)x,并且返回一個(gè)函數(shù),這個(gè)返回函數(shù)接收一個(gè)參數(shù)y,并返回x和y的和。
??實(shí)際上,makeAdder是一個(gè)工廠模式 - 它創(chuàng)建了一個(gè)函數(shù),這個(gè)函數(shù)可以計(jì)算特定值的和。在上面這個(gè)例子當(dāng)中,我們使用工廠模式來(lái)創(chuàng)建新的函數(shù) - 一個(gè)與5進(jìn)行加法運(yùn)算,一個(gè)與10進(jìn)行加法運(yùn)算。add5和add10都是閉包,他們共享相同的函數(shù)定義,但卻存儲(chǔ)著不同的詞法環(huán)境,在add5的詞法環(huán)境當(dāng)中,x為5;在add10的詞法環(huán)境當(dāng)中,x變成了10。
??閉包是很有用的,因?yàn)樗屇惆岩恍?shù)據(jù)(詞法環(huán)境)和一些能夠獲取這些數(shù)據(jù)的函數(shù)聯(lián)系起來(lái),這有點(diǎn)和面向?qū)ο缶幊填愃?,在面向?qū)ο缶幊坍?dāng)中,對(duì)象讓我們可以把一些數(shù)據(jù)(對(duì)象的屬性)和一個(gè)或多個(gè)方法聯(lián)系起來(lái)
??因此,你能夠像對(duì)象的方法一樣隨時(shí)使用閉包。實(shí)際上,大多數(shù)的前端JS代碼都是事件驅(qū)動(dòng)性的 - 我們定義一些事件,當(dāng)這個(gè)事件被用戶所觸發(fā)的時(shí)候(例如用戶的點(diǎn)擊事件和鍵盤事件),我們的事件通常會(huì)帶上一個(gè)回調(diào):即事件觸發(fā)所執(zhí)行的函數(shù)。例如,假設(shè)我們希望在頁(yè)面上添加一些按鈕,這些按鈕能夠調(diào)整文字的大小,實(shí)現(xiàn)這個(gè)功能的方式是確定body的字體大小,然后再設(shè)置頁(yè)面上其他元素(例如標(biāo)題)的字體大小,我們使用em作為單位。
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; }
??我們?cè)O(shè)置的調(diào)節(jié)字體大小的按鈕能夠改變body的font-size,并且這個(gè)調(diào)節(jié)能夠通過(guò)相對(duì)字體單位,反應(yīng)到其他元素上,
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是三個(gè)分別把字體大小調(diào)整為12,14,16的函數(shù),我們可以把他們綁定在按鈕上。
document.getElementById("size-12").onclick = size12; document.getElementById("size-14").onclick = size14; document.getElementById("size-16").onclick = size16;
12 14 16通過(guò)閉包來(lái)封裝私有方法
??類似JAVA語(yǔ)言能夠聲明私有方法,意味著只能夠在相同的類里面被調(diào)用,JS無(wú)法做到這一點(diǎn),但卻可以通過(guò)閉包來(lái)封裝私有方法。私有方法不限制代碼:他們提供了管理命名空間的一種強(qiáng)有力方式。
??下面代碼闡述了怎樣使用閉包來(lái)定義公有函數(shù),公有函數(shù)能夠訪問(wèn)私有方法和屬性。
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
??在先前的例子當(dāng)中,每個(gè)閉包具有他們自己的詞法環(huán)境,在這個(gè)例子中,我們創(chuàng)建了一個(gè)多帶帶的詞法環(huán)境,這個(gè)詞法環(huán)境被3個(gè)函數(shù)所共享,這三個(gè)函數(shù)是counter.increment, counter.decrement和counter.value
??共享的詞法環(huán)境是由匿名函數(shù)創(chuàng)建的,一定義就可以被執(zhí)行,詞法環(huán)境包含兩項(xiàng):變量privateCounter和函數(shù)changeBy,這些私有方法和屬性不能夠被外面訪問(wèn)到,然而,他們能夠被返回的公共函數(shù)訪問(wèn)到。這三個(gè)公有函數(shù)就是閉包,共享相同的環(huán)境,JS的詞法作用域的好處就是他們可以互相訪問(wèn)變量privateCounter和changeBy函數(shù)
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 */
??兩個(gè)計(jì)數(shù)器counter1和counter2分別是互相獨(dú)立的,每個(gè)閉包具有不同版本的privateCounter,每次計(jì)數(shù)器被調(diào)用,詞法環(huán)境會(huì)改變變量的值,但是一個(gè)閉包里變量值的改變并不影響另一個(gè)閉包里的變量。
循環(huán)中創(chuàng)建閉包:常見(jiàn)錯(cuò)誤下面一個(gè)例子
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 數(shù)組定義了三個(gè)有用的hint,每個(gè)分別與輸入框的id相對(duì)應(yīng),每個(gè)方法與onfocus事件綁定起來(lái)。當(dāng)你運(yùn)行這段代碼的時(shí)候,不會(huì)像預(yù)期的那樣工作,不管你聚焦在哪個(gè)輸入框,始終顯示你的age信息。
??原因在于,分配給onfocus事件的函數(shù)是閉包,他們由函數(shù)定義構(gòu)成,從setupHelp函數(shù)的函數(shù)作用域獲取。三個(gè)閉包由循環(huán)所創(chuàng)建,每個(gè)閉包具有同一個(gè)詞法環(huán)境,環(huán)境中包含一個(gè)變量item.help,當(dāng)onfocus的回調(diào)執(zhí)行時(shí),item.help的值也隨之確定,循環(huán)已經(jīng)執(zhí)行完畢,item對(duì)象已經(jīng)指向了helpText列表的最后一項(xiàng)。解決這個(gè)問(wèn)題的方法是使用更多的閉包,具體點(diǎn)就是提前使用一個(gè)封裝好的函數(shù):
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();
??上面代碼運(yùn)行正常,回調(diào)此時(shí)不共享一個(gè)詞法環(huán)境,makeHelpCallback函數(shù)給每個(gè)回調(diào)創(chuàng)造了一個(gè)詞法環(huán)境,詞法環(huán)境中的help指helpText數(shù)組中對(duì)應(yīng)的字符串,使用匿名閉包來(lái)重寫的例子如下:
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關(guān)鍵字
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();
??這個(gè)例子使用let代替var,所以,每個(gè)閉包綁定了塊級(jí)作用域,也就意味著不需要額外的閉包
性能考慮??如果閉包在實(shí)際案例中是不被允許的,在一個(gè)函數(shù)中就不一定再創(chuàng)建一個(gè)函數(shù),因?yàn)檫@會(huì)影響腳本的性能,例如處理的速度和內(nèi)存的消耗。例如,當(dāng)創(chuàng)建一個(gè)對(duì)象,對(duì)象的方法應(yīng)該跟對(duì)象的原型聯(lián)系起來(lái)而不是在對(duì)象的構(gòu)造器里定義,這是因?yàn)闊o(wú)論什么時(shí)候構(gòu)造器被調(diào)用,方法都會(huì)被重新分配
下面一個(gè)例子
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }
前面的代碼沒(méi)有充分利用閉包,我們重寫如下
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } };
??然而,我們不建議重新定義原型,下面的例子中,給原型分別定義方法而不是重新定義整個(gè)原型,這樣會(huì)改變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; };
??在前面兩個(gè)例子中,繼承原型可以被所有對(duì)象所共享并且在每個(gè)對(duì)象創(chuàng)建的同時(shí)都不必定義方法。
參考MDN closure
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/115817.html
摘要:原因在于,分配給事件的函數(shù)是閉包,他們由函數(shù)定義構(gòu)成,從函數(shù)的函數(shù)作用域獲取。三個(gè)閉包由循環(huán)所創(chuàng)建,每個(gè)閉包具有同一個(gè)詞法環(huán)境,環(huán)境中包含一個(gè)變量當(dāng)?shù)幕卣{(diào)執(zhí)行時(shí),的值也隨之確定,循環(huán)已經(jīng)執(zhí)行完畢,對(duì)象已經(jīng)指向了列表的最后一項(xiàng)。 閉包(Closure) 閉包是一個(gè)函數(shù)和詞法環(huán)境的組合,函數(shù)聲明在這個(gè)詞法環(huán)境中 詞法作用域 看下面一個(gè)例子 function init() { var n...
摘要:原因在于,分配給事件的函數(shù)是閉包,他們由函數(shù)定義構(gòu)成,從函數(shù)的函數(shù)作用域獲取。三個(gè)閉包由循環(huán)所創(chuàng)建,每個(gè)閉包具有同一個(gè)詞法環(huán)境,環(huán)境中包含一個(gè)變量當(dāng)?shù)幕卣{(diào)執(zhí)行時(shí),的值也隨之確定,循環(huán)已經(jīng)執(zhí)行完畢,對(duì)象已經(jīng)指向了列表的最后一項(xiàng)。 閉包(Closure) 閉包是一個(gè)函數(shù)和詞法環(huán)境的組合,函數(shù)聲明在這個(gè)詞法環(huán)境中 詞法作用域 看下面一個(gè)例子 function init() { var n...
摘要:再看一段代碼這樣就清晰地展示了閉包的詞法作用域能訪問(wèn)的作用域?qū)?dāng)做一個(gè)值返回執(zhí)行后,將的引用賦值給執(zhí)行,輸出了變量我們知道通過(guò)引用的關(guān)系,就是函數(shù)本身。 在正式學(xué)習(xí)閉包之前,請(qǐng)各位同學(xué)一定要確保自己對(duì)詞法作用域已經(jīng)非常的熟悉了,如果對(duì)詞法作用域還不夠熟悉的話,可以先看: 深入理解閉包之前置知識(shí)---作用域與詞法作用域 前言 現(xiàn)在去面試前端開(kāi)發(fā)的崗位,如果你的面試官也是個(gè)前端,并且不是太...
摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...
摘要:當(dāng)在中調(diào)用匿名函數(shù)時(shí),它們用的都是同一個(gè)閉包,而且在這個(gè)閉包中使用了和的當(dāng)前值的值為因?yàn)檠h(huán)已經(jīng)結(jié)束,的值為。最好將閉包當(dāng)作是一個(gè)函數(shù)的入口創(chuàng)建的,而局部變量是被添加進(jìn)這個(gè)閉包的。 閉包不是魔法 這篇文章使用一些簡(jiǎn)單的代碼例子來(lái)解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實(shí)只要理解了核心概念,閉包并不是那么的難于理解。但是,網(wǎng)上充斥了太多學(xué)術(shù)性的文章,對(duì)于...
閱讀 1568·2021-09-22 15:52
閱讀 3474·2021-09-22 14:59
閱讀 2856·2021-09-02 15:12
閱讀 982·2021-08-20 09:35
閱讀 1590·2019-08-30 14:09
閱讀 2719·2019-08-30 13:56
閱讀 1661·2019-08-26 18:27
閱讀 3373·2019-08-26 13:37