„JavaScript“ uždarymas nuo anoniminių funkcijų

Mano draugas ir aš šiuo metu aptariame, kas yra uždarymas JS ir kas ne. Mes tik norime įsitikinti, kad tai tikrai suprantame teisingai.

Paimkite šį pavyzdį. Turime skaičiavimo ciklą ir norite spausdinti pultelio kintamąjį konsolėje. Todėl, norėdami nustatyti skaitiklio kintamojo vertę, naudojame setTimeout ir uždarymus , siekiant užtikrinti, kad jis nespausdintų N kartų daugiau nei N.

Neteisingas sprendimas be uždarymo ar kažkas šalia uždarymo bus:

 for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); } 

kuris, žinoma, atspausdins 10 kartų didesnę vertę nei po ciklo, ty 10.

Taigi, jo bandymas buvo toks:

 for(var i = 0; i < 10; i++) { (function(){ var i2 = i; setTimeout(function(){ console.log(i2); }, 1000) })(); } 

spausdinkite nuo 0 iki 9, kaip tikėtasi.

Sakiau jam, kad jis nenaudoja uždarymo, kad užfiksuotų i , bet jis primygtinai reikalauja, kad jis būtų. Aš setTimeout kad jis nenaudoja uždarymo , įterpdamas kilpos kūną į kitą setTimeout (perduodamas jo anoniminę funkciją setTimeout ), dar kartą setTimeout 10 kartų 10. Tas pats atsitinka, jei išsaugoju savo funkciją var ir įvykdysiu jį po ciklo, taip pat Aš rašau 10 kartų 10. Todėl mano argumentas yra tai, kad ji tikrai nenustato i vertės , todėl jos versija nėra uždarymas.

Mano bandymas:

 for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); } })(i), 1000); } 

Taigi, aš išsprendžiu i (vadinamas i2 artimiausiu metu), bet dabar grąžinsiu kitą funkciją ir perduodu ją. Mano atveju, funkcija, perduota setTimeout tikrai fiksuoja i .

Dabar kas naudojasi uždarymu ir kas ne?

Atkreipkite dėmesį, kad abu sprendimai spausdinami iš konsolės nuo 0 iki 9, todėl jie išsprendžia pradinę problemą, tačiau norime suprasti, kuris iš šių dviejų sprendimų atlieka uždarymą .

513
17 окт. set leemes 17 okt. 2012-10-17 11:36 '12 11:36 2012-10-17 11:36
@ 11 atsakymų

Redaktoriaus pastaba: visos „JavaScript“ funkcijos yra užrakintos, kaip aprašyta šiame pranešime . Tačiau domina tik šių funkcijų pogrupio apibrėžimas, kuris yra įdomus teoriniu požiūriu. Ateityje bet kokia nuoroda į žodžio uždarymą bus nuoroda į šią funkcijų pogrupį, jei nenurodyta kitaip.

Paprastas uždarymo paaiškinimas:

  • Pasinaudokite funkcija. Tegul jis vadinamas F.
  • Visų kintamųjų sąrašas F.
  • Kintamieji gali būti dviejų tipų:
    • Vietiniai kintamieji (susiję kintamieji)
    • Ne vietiniai kintamieji (laisvi kintamieji)
  • Jei F neturi laisvų kintamųjų, tai negali būti uždarymas.
  • Jei F turi laisvų kintamųjų (kurie apibrėžti tėvų regione F), tada:
    • Turi būti tik vienas tėvų regionas F, prie kurio yra susietas laisvasis kintamasis a .
    • Jei F yra nuoroda iš išorinio , tėvų regiono, tai tampa laisvo kintamojo uždarymu.
    • Laisvas kintamasis vadinamas uždarymo F. ​​verte.

Dabar leiskite mums tai panaudoti, kad išsiaiškintume, kas naudojasi glaudžiais ir kas ne (paaiškinimą pavadinau funkciją):

1 atvejis: jūsų draugo programa

 for (var i = 0; i < 10; i++) { (function f() { var i2 = i; setTimeout(function g() { console.log(i2); }, 1000); })(); } 

Pirmiau minėtoje programoje yra dvi funkcijos: f ir g . Pažiūrėkite, ar jie uždaryti:

f :

  • Rodomi kintamieji:
    • i2 yra vietinis kintamasis .
    • i yra laisvas kintamasis.
    • setTimeout yra nemokamas kintamasis.
    • g yra vietinis kintamasis.
    • console yra laisvas kintamasis.
  • Raskite patronuojančią zoną, kuriai priskiriamas kiekvienas laisvas kintamasis:
    • i susietas su pasauline sritimi.
    • setTimeout susietas su pasauliniu mastu.
    • console susieta su pasauliniu mastu.
  • Kurioje srityje yra nuoroda į funkciją? Pasaulinis regionas .
    • Todėl i nėra uždaryta f .
    • Todėl „ setTimeout nėra uždaryta f .
    • Todėl console nėra uždaryta virš f .

Taigi funkcija f nėra uždarymas.

g :

  • Rodomi kintamieji:
    • console yra laisvas kintamasis.
    • i2 yra laisvas kintamasis.
  • Raskite patronuojančią zoną, kuriai priskiriamas kiekvienas laisvas kintamasis:
    • console susieta su pasauliniu mastu.
    • i2 susietas su i2 f .
  • Kurioje srityje yra nuoroda į funkciją? setTimeout sritis .
    • Todėl console nėra uždaryta g .
    • Todėl i2 uždarytas g .

Taigi funkcija g yra laisvo kintamojo i2 (kuris yra g vertės viršutinė vertė) uždarymas, kai jis yra nustatytassetTimeout .

Blogas jums: jūsų draugas naudoja uždarymą. Vidaus funkcija yra uždarymas.

2 atvejis: Jūsų programa

 for (var i = 0; i < 10; i++) { setTimeout((function f(i2) { return function g() { console.log(i2); }; })(i), 1000); } 

Pirmiau minėtoje programoje yra dvi funkcijos: f ir g . Pažiūrėkite, ar jie uždaryti:

f :

  • Rodomi kintamieji:
    • i2 yra vietinis kintamasis .
    • g yra vietinis kintamasis.
    • console yra laisvas kintamasis.
  • Raskite patronuojančią zoną, kuriai priskiriamas kiekvienas laisvas kintamasis:
    • console susieta su pasauliniu mastu.
  • Kurioje srityje yra nuoroda į funkciją? Pasaulinis regionas .
    • Todėl console nėra uždaryta virš f .

Taigi funkcija f nėra uždarymas.

g :

  • Rodomi kintamieji:
    • console yra laisvas kintamasis.
    • i2 yra laisvas kintamasis.
  • Raskite patronuojančią zoną, kuriai priskiriamas kiekvienas laisvas kintamasis:
    • console susieta su pasauliniu mastu.
    • i2 susietas su i2 f .
  • Kurioje srityje yra nuoroda į funkciją? setTimeout sritis .
    • Todėl console nėra uždaryta g .
    • Todėl i2 uždarytas g .

Taigi funkcija g yra laisvo kintamojo i2 (kuris yra g vertės viršutinė vertė) uždarymas, kai jis yra nustatytassetTimeout .

Jums tinka: naudojate uždarymą. Vidaus funkcija yra uždarymas.

Taigi jūs ir jūsų draugas naudojate uždarymą. Nustokite ginčytis. Tikiuosi, kad išvaliau uždarymo sąvoką ir kaip juos atpažinti.

Redaguoti: paprastas visų funkcijų uždarymo priežasčių paaiškinimas (@Peter kreditai):

Pirma, apsvarstykite šią programą (tai yra kontrolė ):

alternatyva ): 

žr. Šį atsakymą ) - jis neapima message ). 
  • Kai vykdome programą , pastebime , kad message įspėtas.
  • Ką mes tai padarome?

    • „JavaScript“ vertėjai nesiremia uždarymu kitaip nei kitoms funkcijoms.
    • Kiekvienoje funkcijoje yra grandinių grandinė . Uždarymas neturi atskiro nuorodų aplinkos.
    • Uždarymas yra panašus į bet kurią kitą funkciją. Mes paprasčiausiai vadiname juos uždarymais, kai jie yra išvardyti rajone, kuris nepriklauso teritorijai, kuriai jie priklauso , nes tai yra įdomus atvejis.
    609
    17 окт. Atsakymas, kurį pateikė Aadit M Shah Oct 17 2012-10-17 13:01 „12 at 01.01 val. 2012-10-17 13:01

    Pagal closure apibrėžimą:

    „Uždarymas“ yra išraiška (paprastai funkcija), kurioje gali būti laisvi kintamieji kartu su aplinka, kuri susieja šiuos kintamuosius (kurie „uždaro“ išraišką).

    Jei naudojate funkciją, kuri naudoja kintamąjį, apibrėžtą už funkcijos ribų, naudojate closure . (mes vadiname kintamuoju kintamąjį ).
    Visi jie naudoja closure (net ir pirmame pavyzdyje).

    89
    17 окт. atsakymas pateikiamas kev 17 val. 2012-10-17 11:59 '12 11:59 2012-10-17 11:59

    Trumpai tariant, „ Javascript Closures“ leidžia funkcijoms pasiekti kintamąjį , deklaruotą leksikos-tėvų funkcijoje .

    Žr. Išsamesnį paaiškinimą. Norint suprasti uždarymą, svarbu suprasti, kaip „JavaScript“ taikymo sritys yra kintamos.

    Lankytinos vietos

    Funkcijos yra apibrėžtos „JavaScript“ srityse. Kiekviena funkcija apibrėžia naują sritį.

    Apsvarstykite šį pavyzdį:

     function f() {//begin of scope f var foo='hello'; //foo is declared in scope f for(var i=0;i<2;i++){//i is declared in scope f //the for loop is not a function, therefore we are still in scope f var bar = 'Am I accessible?';//bar is declared in scope f console.log(foo); } console.log(i); console.log(bar); }//end of scope f 

    f spausdina skambutį

     hello hello 2 Am I Accessible? 

    Dabar apsvarstykite atvejį, kai turime funkciją g apibrėžtą kitos funkcijos f .

     function f() {//begin of scope f function g() {//being of scope g  }//end of scope g  }//end of scope f 

    Mes vadiname leksinį tėvą g . Kaip paaiškinta anksčiau, dabar turime 2 sritis; plotas f ir plotas g .

    Bet ar viena sritis yra „viduje“ kita apimtis, taip pat vaiko funkcijos apimtis tėvų funkcijos srityje? Kas atsitinka kintamiesiems, deklaruotiems pagrindinės funkcijos srityje; ar galiu juos pasiekti iš vaiko funkcijos? Kur tiksliai yra uždarymas.

    >

    „JavaScript“ sistemoje g funkcija gali ne tik pasiekti bet kokius kintamuosius, deklaruotus „ g domene, bet ir gauti prieigą prie bet kokių kintamųjų, deklaruotų tėvų funkcijos f .

    Apsvarstykite šiuos dalykus:

     function f()//lexical parent function {//begin of scope f var foo='hello'; //foo declared in scope f function g() {//being of scope g var bar='bla'; //bar declared in scope g console.log(foo); }//end of scope g g(); console.log(bar); }//end of scope f 

    f spausdina skambutį

     hello undefined 

    Pažvelkite į liniją console.log(foo); . Šiame etape mes esame domene g ir bandome pasiekti kintamąjį foo deklaruotą domene f . Tačiau, kaip minėta, mes galime pasiekti bet kokį kintamąjį, deklaruotą leksinėje tėvų funkcijoje, kuri vyksta čia; g yra leksinė tėvų f . Todėl jis spausdinamas hello .
    Dabar žiūrėkite linijos console.log(bar); . Šiuo metu mes esame domene f ir bandome pasiekti kintamą bar , deklaruotą domene g . bar nėra deklaruojama dabartiniame regione, o g nėra f tėvas, todėl bar yra neapibrėžta

    Tiesą sakant, mes taip pat galime pasiekti kintamuosius, deklaruotus „didžiojo tėvo“ leksinėje funkcijoje. Todėl, jei funkcijoje g nustatyta funkcija h

     function f() {//begin of scope f function g() {//being of scope g function h() {//being of scope h  }//end of scope h  }//end of scope g  }//end of scope f 

    tada h galės prieiti prie visų kintamųjų, deklaruotų funkcijų h , g ir f domene. Tai daroma baigiant . „JavaScript“ uždarymas leidžia mums pasiekti bet kokį kintamąjį, deklaruotą leksinėje tėvų funkcijoje, leksinėje didžiojo tėvų funkcijoje, leksinėje didžiojo tėvų funkcijoje ir pan. Tai gali būti laikoma grandinių grandine. ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... iki paskutinės pagrindinės funkcijos, neturinčios leksinės tėvų.

    >

    Tiesą sakant, grandinė nesibaigia paskutine pagrindine funkcija. Yra dar viena speciali skalė; visame pasaulyje . Kiekvienas kintamasis, nepateiktas funkcijoje, laikomas deklaruotu pasauliniu mastu. Pasaulinė aprėptis turi du specialybes:

    • bet koks kintamasis, deklaruotas pasauliniu mastu, yra prieinamas visur
    • kintamieji, deklaruoti pagal pasaulinę taikymo sritį, atitinka window objekto savybes.

    Todėl yra tik du būdai deklaruoti kintamąjį foo pasauliniu mastu; arba nenurodydami jos funkcijoje, arba nustatydami >foo turtą.

    Abu bandymai naudojami spynoms

    Dabar, kai perskaitėte išsamesnį paaiškinimą, dabar gali atrodyti akivaizdu, kad abu sprendimai naudoja spyną. Bet, žinoma, padarykime įrodymą.

    Leiskite sukurti naują programavimo kalbą; „Javascript“ nėra uždaryta Kaip rodo pavadinimas, „JavaScript-No-Closure“ yra identiška „JavaScript“, išskyrus tai, kad ji nepalaiko uždarymo.

    Kitaip tariant:

     var foo = 'hello'; function f(){console.log(foo)}; f(); //JavaScript-No-Closure prints undefined //JavaSript prints hello 

    Na, pažiūrėkime, kas atsitinka su pirmuoju sprendimu su „JavaScript-No-Closure“;

     for(var i = 0; i < 10; i++) { (function(){ var i2 = i; setTimeout(function(){ console.log(i2); //i2 is undefined in JavaScript-No-Closure }, 1000) })(); } 

    todėl „JavaScript-No-Closure“ jis bus išspausdintas 10 kartų.

    Todėl pirmasis sprendimas taikomas uždarymui.

    Pažvelkime į antrąjį sprendimą:

     for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); //i2 is undefined in JavaScript-No-Closure } })(i), 1000); } 

    todėl „JavaScript-No-Closure“ jis bus išspausdintas 10 kartų.

    Abu sprendimai naudojami uždarant.

    Redaguoti: Daroma prielaida, kad šie 3 kodo fragmentai nėra apibrėžti pasaulinėje srityje. Priešingu atveju kintamieji foo ir i bus prijungti prie window objekto ir todėl yra prieinami per „JavaScript“ ir „JavaScript-No-Closure“ window objektą.

    46
    17 окт. atsakymas pateikiamas 17 val. 2012-10-17 12:33 '12 12:33 2012-10-17 12:33

    Aš niekada nebuvo patenkintas tuo, kaip kažkas tai paaiškina.

    Svarbiausia suprasti uždarymą yra suprasti, ką JS bus uždaryta.

    Be uždarymo tai sukels klaidą

     function outerFunc(){ var outerVar = 'an outerFunc var'; return function(){ alert(outerVar); } } outerFunc()(); //returns inner function and fires it 

    Kai išorinė funkcija grįžta į įsivaizduojamą uždarą neįgaliųjų „JavaScript“ versiją, nuoroda į išorinį „Var“ surinks šiukšles ir nepalieka nieko, kur yra vidinė funkcija.

    Uždarymas iš tikrųjų yra specialios taisyklės, kurios leidžia ir leidžia šioms linijoms egzistuoti, kai vidinė funkcija reiškia funkcijos išorinius kintamuosius. Uždarymo metu nuorodos, pažymėtos vars, išlieka net ir tada, kai išorinė funkcija yra vykdoma arba „uždaryta“, jei tai padeda prisiminti šį punktą.

    Net uždarant vietinių funkcijų be vietos funkcijų vietos variacijų gyvavimo ciklą, susijusį su jų vietos gyventojais, veikia taip pat, kaip ir uždaroje versijoje. Kai funkcija baigiama, vietiniai gyventojai gauna surinktas šiukšles.

    Kai tik jūs turėsite nuorodą į vidinę var išorinę funkciją, tačiau tai, kaip ir durų staktas, dedama į šiukšlių surinkimo būdą.

    Galbūt tikslesnis būdas apsvarstyti uždarymą yra tai, kad vidaus funkcija iš esmės naudoja vidinę sritį kaip savo taikymo sritį.

    Tačiau su juo susijęs kontekstas iš tikrųjų yra pastovus, o ne kaip momentinė nuotrauka. Pakartotinai įjungus grąžintą vidinę funkciją, kuri toliau didina ir registruoja išorinę funkciją, vietinis var įspėja aukštesnes vertes.

     function outerFunc(){ var incrementMe = 0; return function(){ incrementMe++; console.log(incrementMe); } } var inc = outerFunc(); inc(); //logs 1 inc(); //logs 2 
    19
    18 окт. atsakymas, kurį pateikė Erik Reppen 18 okt. 2012-10-18 04:51 '12 at 4:51 2012-10-18 04:51

    Jūs abu naudojate uždarymą.

    Aš einu su Wikipedia apibrėžimu čia:

    Kompiuterių moksluose uždarymas (taip pat ir leksinis uždarymas arba funkcijų uždarymas) yra funkcija arba funkcijos nuoroda kartu su atskaitos aplinka - lentelė, kurioje saugoma nuoroda į kiekvieną šios vietovės kintamąjį (taip pat vadinamus laisvaisiais kintamaisiais). Uždarymas - priešingai nei paprastas funkcijų rodiklis - leidžia pasiekti šiuos ne vietinius kintamuosius, net jei jie vadinami iš leksinės srities.

    Jūsų draugo bandymas aiškiai naudoja kintamąjį i , kuris yra ne vietinis, atsižvelgiant į jo vertę ir kopiją saugojimui vietiniame i2 .

    Jūsų bandymas praeina i (kuris yra skambučio mazge lauke) į anoniminę funkciją kaip argumentą. Nors tai nėra arti, bet tada ši funkcija grąžina kitą funkciją, kuri reiškia tą patį i2 . Kadangi vidinėje anoniminėje funkcijoje i2 nėra vietinė, ji sukuria uždarymą.

    16
    17 окт. Atsakyti į Jon Oct 17 2012-10-17 12:08 '12 12:08 2012-10-17 12:08

    Jūs ir jūsų draugas naudojate uždarymą:

    Uždarymas yra specialus objektas, jungiantis du dalykus: funkciją ir aplinką, kurioje ši funkcija buvo sukurta. Aplinka susideda iš bet kokių vietinių kintamųjų, kurie buvo taikomi uždarymo metu.

    MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

    Jūsų draugo kodo funkcija, function(){ console.log(i2); } function(){ console.log(i2); } , apibrėžta anoniminės funkcijos uždarymo function(){ var i2 = i; ... function(){ var i2 = i; ... ir gali skaityti / rašyti vietinį kintamąjį i2 .

    Savo funkcijos kode function(){ console.log(i2); } function(){ console.log(i2); } , apibrėžta uždarymo funkcijos function(i2){ return ... , ir gali skaityti / rašyti vietinę vertingą i2 (deklaruota kaip parametras šiuo atveju).

    Abiem atvejais funkcijos function(){ console.log(i2); } function(){ console.log(i2); } tada eina į setTimeout .

    Kitas ekvivalentas (bet mažesnis atminties naudojimas):

     function fGenerator(i2){ return function(){ console.log(i2); } } for(var i = 0; i < 10; i++) { setTimeout(fGenerator(i), 1000); } 
    12
    17 окт. Atsakymą pateikė Andrew D. 17 okt. 2012-10-17 12:03 '12, 12:03, 2012-10-17 12:03

    Uždarymas

    Uždarymas nėra funkcija, o ne išraiška. Jis turėtų būti laikomas kintamųjų, naudojamų už funkcijos ribų ir naudojamų funkcijos viduje, „momentiniu vaizdu“. Gramatiniu požiūriu reikėtų pasakyti: „užsidarykite kintamuosius.“

    Kitaip tariant, uždarymas yra atitinkamų kintamųjų konteksto, nuo kurio priklauso funkcija, kopija.

    Dar kartą (naiv): uždarymas turi prieigą prie kintamųjų, kurie nėra perduoti kaip parametras.

    Atminkite, kad šios funkcinės sąvokos labai priklauso nuo programavimo kalbos / aplinkos, kurią naudojate. „JavaScript“ uždarymas priklauso nuo leksinės taikymo srities (kuri yra teisinga daugelyje c kalbų).

    Taigi, grąžinus funkciją iš esmės grąžinama anoniminė / be pavadinimo funkcija. Kai funkcijų prieigos kintamasis, kuris nebuvo perduotas kaip parametras, ir jo (leksinėje) srityje, buvo uždarytas.

    Taigi, dėl jūsų pavyzdžių:

     // 1 for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); // closure, only when loop finishes within 1000 ms, }, 1000); // i = 10 for all functions } // 2 for(var i = 0; i < 10; i++) { (function(){ var i2 = i; // closure of i (lexical scope: for-loop) setTimeout(function(){ console.log(i2); // closure of i2 (lexical scope:outer function) }, 1000) })(); } // 3 for(var i = 0; i < 10; i++) { setTimeout((function(i2){ return function() { console.log(i2); // closure of i2 (outer scope) } })(i), 1000); // param access i (no closure) }