Penutupan JavaScript di dalam loop - contoh praktis sederhana

2819

var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

Ini menghasilkan ini:

Nilai saya: 3
Nilai saya: 3
Nilai saya: 3

Sedangkan saya ingin hasilnya:

Nilai saya: 0
Nilai saya: 1
Nilai saya: 2


Masalah yang sama terjadi ketika keterlambatan dalam menjalankan fungsi disebabkan oleh penggunaan pendengar acara:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

... atau kode asinkron, misalnya menggunakan Janji:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

Sunting: Ini juga terlihat di dalam for indan for ofloop:

const arr = [1,2,3];
const fns = [];

for(i in arr){
  fns.push(() => console.log(`index: ${i}`));
}

for(v of arr){
  fns.push(() => console.log(`value: ${v}`));
}

for(f of fns){
  f();
}

Apa solusi untuk masalah mendasar ini?

nickf
sumber
55
Anda yakin tidak ingin funcsmenjadi array, jika Anda menggunakan indeks numerik? Hanya kepala.
DanMan
23
Ini benar-benar masalah yang membingungkan. Artikel ini membantu saya dalam memahaminya . Mungkin itu membantu orang lain juga.
user3199690
4
Solusi sederhana dan jelas lainnya: 1) Fungsi Bersarang memiliki akses ke ruang lingkup "di atas" mereka ; 2) a penutupan solusi ... "Sebuah penutupan adalah fungsi memiliki akses ke ruang lingkup orangtua, bahkan setelah fungsi orangtua telah ditutup".
Peter Krauss
2
Rujuk tautan ini untuk lebih memahami javascript.info/tutorial/advanced-functions
Saurabh Ahuja
35
Dalam ES6 , solusi sepele adalah mendeklarasikan variabel i dengan membiarkan , yang dicakup dalam tubuh loop.
Tomas Nikodym

Jawaban:

2149

Nah, masalahnya adalah bahwa variabel i, dalam setiap fungsi anonim Anda, terikat pada variabel yang sama di luar fungsi.

Solusi klasik: Penutupan

Apa yang ingin Anda lakukan adalah mengikat variabel di dalam setiap fungsi ke nilai yang terpisah dan tidak berubah di luar fungsi:

var funcs = [];

function createfunc(i) {
  return function() {
    console.log("My value: " + i);
  };
}

for (var i = 0; i < 3; i++) {
  funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

Karena tidak ada ruang lingkup blok dalam JavaScript - hanya ruang lingkup fungsi - dengan membungkus pembuatan fungsi dalam fungsi baru, Anda memastikan bahwa nilai "i" tetap seperti yang Anda inginkan.


Solusi ES5.1: forEach

Dengan ketersediaan Array.prototype.forEachfungsi yang relatif luas (pada 2015), perlu dicatat bahwa dalam situasi-situasi yang melibatkan iterasi terutama atas berbagai nilai, .forEach()memberikan cara yang bersih dan alami untuk mendapatkan penutupan yang berbeda untuk setiap iterasi. Yaitu, dengan asumsi Anda punya semacam array yang berisi nilai (referensi DOM, objek, apa pun), dan masalah muncul saat menyiapkan panggilan balik khusus untuk setiap elemen, Anda dapat melakukan ini:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

Idenya adalah bahwa setiap pemanggilan fungsi callback yang digunakan dengan .forEachloop akan menjadi penutupannya sendiri. Parameter yang diteruskan ke handler itu adalah elemen array yang spesifik untuk langkah iterasi tertentu. Jika digunakan dalam callback asinkron, itu tidak akan bertabrakan dengan salah satu dari callback lain yang dibuat pada langkah lain dari iterasi.

Jika Anda kebetulan bekerja di jQuery, $.each()fungsinya memberi Anda kemampuan yang sama.


Solusi ES6: let

ECMAScript 6 (ES6) memperkenalkan kata kunci baru letdan constyang memiliki cakupan berbeda dari varvariabel berbasis. Misalnya, dalam loop dengan letindeks berbasis, setiap iterasi melalui loop akan memiliki variabel baru idengan cakupan loop, sehingga kode Anda akan berfungsi seperti yang Anda harapkan. Ada banyak sumber daya, tetapi saya akan merekomendasikan pos scoping 2ality sebagai sumber informasi yang bagus.

for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}

Berhati-hatilah, bagaimanapun juga, bahwa IE9-IE11 dan Edge sebelum Edge 14 mendukung lettetapi kesalahan di atas (mereka tidak membuat yang baru isetiap kali, jadi semua fungsi di atas akan masuk log 3 seperti yang akan mereka lakukan jika kami gunakan var). Edge 14 akhirnya berhasil dengan benar.

harto
sumber
7
function createfunc(i) { return function() { console.log("My value: " + i); }; }masih belum ditutup karena menggunakan variabel i?
ア レ ッ ク ス
55
Sayangnya, jawaban ini sudah usang dan tidak ada yang akan melihat jawaban yang benar di bagian bawah - menggunakan Function.bind()pasti lebih disukai sekarang, lihat stackoverflow.com/a/19323214/785541 .
Wladimir Palant
81
@Wladimir: Saran Anda bahwa .bind()adalah "jawaban yang benar" tidak benar. Mereka masing-masing memiliki tempat sendiri. Dengan .bind()Anda tidak dapat mengikat argumen tanpa mengikat thisnilainya. Anda juga mendapatkan salinan iargumen tanpa kemampuan untuk mengubahnya di antara panggilan, yang kadang-kadang diperlukan. Jadi mereka adalah konstruksi yang sangat berbeda, belum lagi .bind()implementasi yang secara historis lambat. Tentu dalam contoh sederhana akan berhasil, tetapi penutupan adalah konsep penting untuk dipahami, dan itulah pertanyaannya.
Cookie Monster
8
Harap hentikan penggunaan peretasan fungsi pengembalian ini, gunakan [] .forEach atau [] .map sebagai gantinya karena mereka menghindari penggunaan kembali variabel lingkup yang sama.
Christian Landgren
32
@ChristianLandgren: Itu hanya berguna jika Anda mengulang Array. Teknik-teknik ini bukan "retasan". Mereka adalah pengetahuan esensial.
379

Mencoba:

var funcs = [];
    
for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Edit (2014):

Secara pribadi saya pikir @ Aust jawaban.bind yang lebih baru tentang penggunaan adalah cara terbaik untuk melakukan hal semacam ini sekarang. Ada juga lo-dash / garis bawah ini _.partialketika Anda tidak perlu atau ingin main-main dengan bind's thisArg.

Bjorn
sumber
2
ada penjelasan tentang }(i));?
aswzen
3
@ aswzen Saya pikir ini berlaku isebagai argumen indexke fungsi.
Jet Blue
sebenarnya membuat indeks variabel lokal.
Abhishek Singh
1
Segera Meminta Function Expression, alias IIFE. (i) adalah argumen untuk ekspresi fungsi anonim yang dipanggil segera dan indeks ditetapkan dari i.
Telur
348

Cara lain yang belum disebutkan adalah penggunaan Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

MEMPERBARUI

Seperti yang ditunjukkan oleh @squint dan @mekdev, Anda mendapatkan kinerja yang lebih baik dengan membuat fungsi di luar loop terlebih dahulu dan kemudian mengikat hasilnya di dalam loop.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}

Aust
sumber
Inilah yang saya lakukan hari ini juga, saya juga suka lo-dash / garis bawah_.partial
Bjorn
17
.bind()sebagian besar akan usang dengan fitur ECMAScript 6. Selain itu, ini sebenarnya menciptakan dua fungsi per iterasi. Pertama anonim, lalu yang dihasilkan oleh .bind(). Penggunaan yang lebih baik adalah membuatnya di luar loop, lalu .bind()di dalam.
5
@squint @mekdev - Anda berdua benar. Contoh awal saya ditulis dengan cepat untuk menunjukkan bagaimana binddigunakan. Saya telah menambahkan contoh lain sesuai saran Anda.
Aust
5
Saya pikir daripada membuang-buang perhitungan lebih dari dua loop O (n), lakukan saja untuk (var i = 0; i <3; i ++) {log.call (this, i); }
user2290820
1
.bind () melakukan apa yang disarankan oleh jawaban yang diterima ditambah dengan biola this.
niry
269

Menggunakan Ekspresi Fungsi Segera-Invoked , cara paling sederhana dan paling mudah dibaca untuk melampirkan variabel indeks:

for (var i = 0; i < 3; i++) {

    (function(index) {

        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value:   $.ajax({});
    
    })(i);

}

Ini mengirimkan iterator ike fungsi anonim yang kami definisikan sebagai index. Ini menciptakan penutupan, di mana variabel idisimpan untuk digunakan nanti dalam fungsi asinkron apa pun dalam IIFE.

neurosnap
sumber
10
Untuk keterbacaan kode lebih lanjut dan untuk menghindari kebingungan tentang iapa, saya akan mengubah nama parameter fungsi menjadi index.
Kyle Falconer
5
Bagaimana Anda menggunakan teknik ini untuk mendefinisikan funcs array yang dijelaskan dalam pertanyaan asli?
Nico
@Nico Cara yang sama seperti yang ditunjukkan pada pertanyaan awal, kecuali Anda akan menggunakan indexsebagai gantinya i.
JLRishe
@JLRishevar funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
Nico
1
@Nico Dalam kasus khusus OP, mereka hanya mengulangi angka, jadi ini bukan kasus yang bagus untuk .forEach(), tetapi sering kali, ketika seseorang memulai dengan array, forEach()adalah pilihan yang baik, seperti:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
JLRishe
164

Agak terlambat ke pesta, tapi saya sedang mengeksplorasi masalah ini hari ini dan memperhatikan bahwa banyak jawaban tidak sepenuhnya membahas bagaimana Javascript memperlakukan ruang lingkup, yang pada dasarnya adalah intinya.

Jadi seperti yang banyak disebutkan, masalahnya adalah bahwa fungsi bagian dalam merujuk ivariabel yang sama . Jadi mengapa kita tidak hanya membuat variabel lokal baru setiap iterasi, dan memiliki referensi fungsi bagian dalamnya?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Sama seperti sebelumnya, di mana setiap fungsi batin menghasilkan nilai terakhir yang ditetapkan i, sekarang setiap fungsi batin hanya menampilkan nilai terakhir yang ditetapkan ilocal. Tapi bukankah setiap iterasi memiliki itu sendiri ilocal?

Ternyata, itulah masalahnya. Setiap iterasi berbagi ruang lingkup yang sama, sehingga setiap iterasi setelah yang pertama hanya menimpa ilocal. Dari MDN :

Penting: JavaScript tidak memiliki cakupan blok. Variabel yang diperkenalkan dengan blok dicakup oleh fungsi atau skrip yang berisi, dan efek pengaturannya tetap ada di luar blok itu sendiri. Dengan kata lain, pernyataan blok tidak memperkenalkan ruang lingkup. Meskipun blok "standalone" adalah sintaks yang valid, Anda tidak ingin menggunakan blok standalone dalam JavaScript, karena mereka tidak melakukan apa yang Anda pikir mereka lakukan, jika Anda berpikir mereka melakukan sesuatu seperti blok seperti di C atau Java.

Diulangi untuk penekanan:

JavaScript tidak memiliki ruang lingkup blokir. Variabel yang diperkenalkan dengan blok dicakup oleh fungsi atau skrip yang berisi

Kita dapat melihat ini dengan memeriksa ilocalsebelum menyatakannya di setiap iterasi:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

Inilah mengapa bug ini sangat rumit. Meskipun Anda mendeklarasikan ulang variabel, Javascript tidak akan menampilkan kesalahan, dan JSLint bahkan tidak akan memberikan peringatan. Ini juga mengapa cara terbaik untuk menyelesaikan ini adalah dengan mengambil keuntungan dari penutupan, yang pada dasarnya adalah gagasan bahwa dalam Javascript, fungsi dalam memiliki akses ke variabel luar karena cakupan dalam "menyertakan" cakupan luar.

Penutupan

Ini juga berarti bahwa fungsi dalam "memegang" variabel luar dan membuatnya tetap hidup, bahkan jika fungsi luar kembali. Untuk memanfaatkan ini, kami membuat dan memanggil fungsi wrapper murni untuk membuat lingkup baru, mendeklarasikan ilocaldalam lingkup baru, dan mengembalikan fungsi bagian dalam yang menggunakan ilocal(penjelasan lebih lanjut di bawah):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Menciptakan fungsi dalam di dalam fungsi pembungkus memberikan fungsi internal lingkungan pribadi yang hanya dapat diakses, sebuah "penutupan". Jadi, setiap kali kita memanggil fungsi wrapper, kita membuat fungsi internal baru dengan lingkungannya sendiri yang terpisah, memastikan bahwa ilocalvariabel tidak saling bertabrakan dan saling menimpa. Beberapa optimasi kecil memberikan jawaban akhir yang diberikan banyak pengguna SO lainnya:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

Memperbarui

Dengan ES6 sekarang menjadi arus utama, kami sekarang dapat menggunakan letkata kunci baru untuk membuat variabel blok-tertutup:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

Lihatlah betapa mudahnya sekarang! Untuk informasi lebih lanjut, lihat jawaban ini , yang menjadi dasar info saya.

woojoo666
sumber
Saya suka bagaimana Anda menjelaskan cara IIFE juga. Saya mencari itu. Terima kasih.
CapturedTree
4
Sekarang ada yang namanya pelingkupan blok dalam JavaScript menggunakan kata kunci letdan const. Jika jawaban ini diperluas untuk memasukkan itu, itu akan jauh lebih bermanfaat secara global menurut saya.
@TinyGiant hal yang pasti, saya menambahkan beberapa info tentang letdan menautkan penjelasan yang lebih lengkap
woojoo666
@ woojoo666 Bisa jawaban Anda juga bekerja untuk memanggil dua URL bolak ada di lingkaran seperti: i=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }? (bisa mengganti window.open () dengan getelementbyid ......)
gila tentang
@nuttyaboutnatty maaf tentang balasan yang terlambat. Sepertinya kode dalam contoh Anda sudah tidak berfungsi. Anda tidak menggunakan idalam fungsi batas waktu Anda, jadi Anda tidak perlu penutupan
woojoo666
151

Dengan ES6 sekarang didukung secara luas, jawaban terbaik untuk pertanyaan ini telah berubah. ES6 menyediakan letdan constkata kunci untuk keadaan yang tepat ini. Alih-alih bermain-main dengan penutupan, kita bisa menggunakan letuntuk mengatur variabel lingkup loop seperti ini:

var funcs = [];

for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

valkemudian akan menunjuk ke objek yang khusus untuk putaran tertentu dari loop, dan akan mengembalikan nilai yang benar tanpa notasi penutupan tambahan. Ini jelas sangat menyederhanakan masalah ini.

constmirip dengan letdengan pembatasan tambahan bahwa nama variabel tidak dapat dikembalikan ke referensi baru setelah penugasan awal.

Dukungan browser kini ada di sini untuk mereka yang menargetkan versi browser terbaru. constSaya letsaat ini didukung di Firefox, Safari, Edge, dan Chrome terbaru. Ini juga didukung di Node, dan Anda dapat menggunakannya di mana saja dengan memanfaatkan alat bantu bangunan seperti Babel. Anda dapat melihat contohnya di sini: http://jsfiddle.net/ben336/rbU4t/2/

Dokumen di sini:

Berhati-hatilah, bagaimanapun juga, bahwa IE9-IE11 dan Edge sebelum Edge 14 mendukung lettetapi kesalahan di atas (mereka tidak membuat yang baru isetiap kali, jadi semua fungsi di atas akan masuk log 3 seperti yang akan mereka lakukan jika kami gunakan var). Edge 14 akhirnya berhasil dengan benar.

Ben McCormick
sumber
Sayangnya, 'let' masih belum sepenuhnya didukung, terutama di ponsel. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MattC
2
Pada 16 Juni, mari didukung di semua versi browser utama kecuali iOS Safari, Opera Mini dan Safari 9. Browser Evergreen mendukungnya. Babel akan mengubahnya dengan benar untuk menjaga perilaku yang diharapkan tanpa mode kepatuhan tinggi diaktifkan.
Dan Pantry
@DanPantry ya tentang waktu untuk pembaruan :) Diperbarui untuk lebih mencerminkan keadaan saat ini, termasuk menambahkan penyebutan const, tautan dokumen dan info kompatibilitas yang lebih baik.
Ben McCormick
Bukankah ini sebabnya kami menggunakan babel untuk mengubah kode kami sehingga peramban yang tidak mendukung ES6 / 7 dapat memahami apa yang terjadi?
piksel 67
87

Cara lain untuk mengatakannya adalah bahwa ifungsi Anda terikat pada saat menjalankan fungsi, bukan saat membuat fungsi.

Saat Anda membuat penutupan, i adalah referensi ke variabel yang ditentukan dalam lingkup luar, bukan salinannya seperti ketika Anda membuat penutupan. Ini akan dievaluasi pada saat eksekusi.

Sebagian besar jawaban lain menyediakan cara untuk bekerja dengan membuat variabel lain yang tidak akan mengubah nilai untuk Anda.

Saya pikir saya akan menambahkan penjelasan untuk kejelasan. Sebagai solusi, secara pribadi, saya akan pergi dengan Harto karena ini adalah cara paling jelas untuk melakukannya dari jawaban di sini. Salah satu kode yang diposting akan berfungsi, tetapi saya akan memilih pabrik penutupan daripada harus menulis setumpuk komentar untuk menjelaskan mengapa saya mendeklarasikan variabel baru (Freddy dan 1800-an) atau memiliki sintaksis penutupan tersemat yang aneh (apphacker).

Darren Clark
sumber
71

Yang perlu Anda pahami adalah ruang lingkup variabel dalam javascript berdasarkan pada fungsi. Ini adalah perbedaan penting dari katakanlah c # di mana Anda memiliki ruang lingkup blok, dan hanya menyalin variabel ke dalam untuk akan bekerja.

Membungkusnya dalam fungsi yang mengevaluasi mengembalikan fungsi seperti jawaban apphacker akan melakukan trik, karena variabel sekarang memiliki cakupan fungsi.

Ada juga kata kunci let bukannya var, yang akan memungkinkan menggunakan aturan ruang lingkup blok. Dalam hal mendefinisikan variabel di dalam untuk akan melakukan trik. Karena itu, kata kunci let bukan solusi praktis karena kompatibilitas.

var funcs = {};

for (var i = 0; i < 3; i++) {
  let index = i; //add this
  funcs[i] = function() {
    console.log("My value: " + index); //change to the copy
  };
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}

eglasius
sumber
@nickf browser yang mana? seperti yang saya katakan, ia memiliki masalah kompatibilitas, dengan yang saya maksud masalah kompatibilitas serius, seperti saya tidak berpikir biarkan didukung di IE.
eglasius
1
@nickf ya, periksa referensi ini: developer.mozilla.org/En/New_in_JavaScript_1.7 ... periksa bagian definisi let, ada contoh onclick di dalam sebuah loop
eglasius
2
@nickf hmm, sebenarnya Anda harus secara eksplisit menentukan versi: <script type = "application / javascript; versi = 1.7" /> ... Saya belum menggunakannya di mana pun karena pembatasan IE, hanya saja tidak praktis :(
eglasius
Anda dapat melihat dukungan peramban untuk berbagai versi di sini es.wikipedia.org/wiki/Javascript
eglasius
59

Berikut variasi lain pada teknik, mirip dengan Bjorn's (apphacker), yang memungkinkan Anda menetapkan nilai variabel di dalam fungsi daripada meneruskannya sebagai parameter, yang kadang-kadang mungkin lebih jelas:

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

Perhatikan bahwa teknik apa pun yang Anda gunakan, indexvariabel menjadi semacam variabel statis, terikat pada salinan yang dikembalikan dari fungsi dalam. Yaitu, perubahan nilainya dipertahankan antara panggilan. Ini bisa sangat berguna.

Boann
sumber
Terima kasih dan solusi Anda berfungsi. Tetapi saya ingin bertanya mengapa ini berhasil, tetapi mengganti varsaluran dan returnsaluran tidak akan berfungsi? Terima kasih!
midnite
@midnite Jika Anda bertukar vardan returnvariabel tidak akan ditugaskan sebelum mengembalikan fungsi batin.
Boann
53

Ini menjelaskan kesalahan umum dengan menggunakan penutupan dalam JavaScript.

Fungsi mendefinisikan lingkungan baru

Mempertimbangkan:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

Untuk setiap kali makeCounterdipanggil, {counter: 0}menghasilkan objek baru yang dibuat. Juga, salinan baru obj dibuat juga untuk referensi objek baru. Jadi, counter1dancounter2 tidak tergantung satu sama lain.

Penutupan dalam loop

Menggunakan penutupan dalam satu lingkaran itu sulit.

Mempertimbangkan:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

Perhatikan bahwa counters[0]dan counters[1]yang tidak independen. Bahkan, mereka beroperasi dengan cara yang sama obj!

Ini karena hanya ada satu salinan yang objdibagikan di semua iterasi dari loop, mungkin karena alasan kinerja. Meskipun {counter: 0}membuat objek baru di setiap iterasi, salinan yang sama objhanya akan diperbarui dengan referensi ke objek terbaru.

Solusinya adalah dengan menggunakan fungsi pembantu lain:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

Ini berfungsi karena variabel lokal dalam lingkup fungsi secara langsung, serta variabel argumen fungsi, dialokasikan salinan baru saat entri.


sumber
Klarifikasi kecil: Pada contoh pertama Closures in loop, penghitung [0] dan penghitung [1] tidak independen bukan karena alasan kinerja. Alasannya adalah bahwa var obj = {counter: 0};dievaluasi sebelum kode dijalankan seperti yang dinyatakan dalam: MDN var : deklarasi var, di mana pun mereka terjadi, diproses sebelum kode apa pun dijalankan.
Charidimos
50

Solusi paling sederhana adalah,

Alih-alih menggunakan:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

yang memberi peringatan "2", selama 3 kali. Ini karena fungsi anonim dibuat untuk loop, berbagi penutupan yang sama, dan dalam penutupan itu, nilai iadalah sama. Gunakan ini untuk mencegah penutupan bersama:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

Gagasan di balik ini adalah, merangkum seluruh tubuh for for dengan IIFE (Immediately-Invoked Function Expression) dan meneruskan new_isebagai parameter dan menangkapnya sebagai i. Karena fungsi anonim dieksekusi segera, inilainya berbeda untuk setiap fungsi yang didefinisikan di dalam fungsi anonim.

Solusi ini tampaknya sesuai dengan masalah seperti itu karena akan membutuhkan perubahan minimal pada kode asli yang menderita masalah ini. Faktanya, ini adalah desain, seharusnya tidak menjadi masalah sama sekali!

Kemal Dağ
sumber
2
Baca sesuatu yang serupa di buku sekali. Saya lebih suka ini, karena Anda tidak harus menyentuh kode yang ada (sebanyak) dan menjadi jelas mengapa Anda melakukannya, setelah Anda mempelajari pola fungsi self-calling: untuk menjebak variabel itu di yang baru dibuat cakupan.
DanMan
1
@DanMan Terima kasih. Fungsi anonim yang memanggil sendiri adalah cara yang sangat baik untuk mengatasi kurangnya ruang lingkup variabel level javascript.
Kemal Dağ
3
Menyebut diri sendiri, atau memohon sendiri bukanlah istilah yang tepat untuk teknik ini, IIFE (Ekspresi Fungsi yang Segera Diminta) lebih akurat. Ref: benalman.com/news/2010/11/...
jherax
31

coba yang lebih pendek ini

  • tidak ada susunan

  • tidak ada tambahan untuk loop


for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/

yilmazburk
sumber
1
Solusi Anda tampaknya menghasilkan yang benar tetapi tidak menggunakan fungsi, mengapa tidak hanya console.log output? Pertanyaan aslinya adalah tentang pembuatan fungsi anonim yang memiliki penutupan yang sama. Masalahnya adalah, karena mereka memiliki satu penutupan, nilai i sama untuk masing-masing. Saya harap kamu mengerti.
Kemal Dağ
30

Berikut adalah solusi sederhana yang menggunakan forEach(berfungsi kembali ke IE9):

var funcs = [];
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Cetakan:

My value: 0
My value: 1
My value: 2
Daryl
sumber
27

Masalah utama dengan kode yang ditunjukkan oleh OP adalah yang itidak pernah dibaca sampai loop kedua. Untuk menunjukkan, bayangkan melihat kesalahan di dalam kode

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

Kesalahan sebenarnya tidak terjadi sampai funcs[someIndex]dijalankan (). Menggunakan logika yang sama ini, harus jelas bahwa nilai ijuga tidak dikumpulkan sampai titik ini juga. Setelah loop asli selesai, i++bawa ike nilai 3yang menghasilkan kondisi i < 3gagal dan loop berakhir. Pada titik ini, iadalah 3dan kapan funcs[someIndex]()digunakan, dani dievaluasi, itu adalah 3 - setiap kali.

Untuk melewati ini, Anda harus mengevaluasi iseperti yang ditemui. Perhatikan bahwa ini sudah terjadi dalam bentukfuncs[i] (di mana ada 3 indeks unik). Ada beberapa cara untuk menangkap nilai ini. Salah satunya adalah meneruskannya sebagai parameter ke fungsi yang ditunjukkan dalam beberapa cara sudah ada di sini.

Pilihan lain adalah untuk membangun objek fungsi yang akan dapat menutup variabel. Itu bisa dicapai dengan demikian

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
Travis J
sumber
23

Fungsi JavaScript "menutup" ruang lingkup yang mereka miliki aksesnya setelah deklarasi, dan mempertahankan akses ke ruang lingkup itu bahkan ketika variabel dalam ruang lingkup itu berubah.

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

Setiap fungsi dalam array di atas menutup cakupan global (global, hanya karena itu adalah lingkup yang dideklarasikan).

Kemudian fungsi-fungsi tersebut dipanggil mencatat nilai terbaru idalam lingkup global. Itulah keajaiban, dan frustrasi, dari penutupan.

"Fungsi JavaScript menutup lingkup tempat mereka dideklarasikan, dan mempertahankan akses ke lingkup itu bahkan ketika nilai variabel di dalam lingkup itu berubah."

Menggunakan letalih-alih varmenyelesaikan ini dengan membuat lingkup baru setiap kali forloop berjalan, membuat lingkup terpisah untuk setiap fungsi untuk ditutup. Berbagai teknik lain melakukan hal yang sama dengan fungsi ekstra.

var funcs = []

for (let i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

( letmembuat variabel dilingkari blok. Blok dilambangkan dengan kurung kurawal, tetapi dalam kasus for loop, variabel inisialisasi, idalam kasus kami, dianggap dideklarasikan dalam kurung kurawal.)

Costa
sumber
1
Saya berjuang untuk memahami konsep ini sampai saya membaca jawaban ini. Menyentuh pada titik yang sangat penting - nilai isedang diatur ke ruang lingkup global. Ketika forloop selesai berjalan, nilai global isekarang adalah 3. Oleh karena itu, setiap kali fungsi itu dipanggil dalam array (menggunakan, katakanlah funcs[j]), idalam fungsi itu merujuk ivariabel global (yaitu 3).
Modermo
13

Setelah membaca berbagai solusi, saya ingin menambahkan bahwa alasan solusi tersebut bekerja adalah bergantung pada konsep rantai ruang lingkup . Begitulah cara JavaScript menyelesaikan suatu variabel selama eksekusi.

  • Setiap definisi fungsi membentuk ruang lingkup yang terdiri dari semua variabel lokal yang dideklarasikan oleh vardan arguments.
  • Jika kita memiliki fungsi dalam didefinisikan di dalam fungsi (luar) lain, ini membentuk rantai, dan akan digunakan selama eksekusi
  • Ketika suatu fungsi dieksekusi, runtime mengevaluasi variabel dengan mencari rantai lingkup . Jika suatu variabel dapat ditemukan pada titik tertentu dari rantai itu, ia akan berhenti mencari dan menggunakannya, jika tidak, ia berlanjut sampai lingkup global yang dimiliki window.

Dalam kode awal:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

Ketika funcsdieksekusi, rantai lingkup akan function inner -> global. Karena variabel itidak dapat ditemukan di function inner(tidak dideklarasikan menggunakan varatau dilewatkan sebagai argumen), ia terus mencari, sampai nilai iakhirnya ditemukan dalam lingkup global yang window.i.

Dengan membungkusnya dalam fungsi luar baik secara eksplisit mendefinisikan fungsi pembantu seperti harto lakukan atau menggunakan fungsi anonim seperti yang dilakukan Bjorn :

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

Ketika funcsdieksekusi, sekarang rantai cakupan akan function inner -> function outer. Waktu ini idapat ditemukan dalam lingkup fungsi luar yang dieksekusi 3 kali dalam for loop, setiap kali memiliki nilai iterikat dengan benar. Itu tidak akan menggunakan nilai window.iketika batin dieksekusi.

Lebih detail dapat ditemukan di sini.
Ini termasuk kesalahan umum dalam membuat penutupan dalam loop seperti apa yang kita miliki di sini, serta mengapa kita perlu penutupan dan pertimbangan kinerja.

wpding
sumber
Kami jarang menulis contoh kode ini secara nyata, tetapi saya pikir ini merupakan contoh yang baik untuk memahami dasar-dasarnya. Begitu kita memiliki ruang lingkup dalam pikiran dan bagaimana mereka dirantai bersama, akan lebih jelas untuk melihat mengapa cara-cara 'modern' lainnya seperti Array.prototype.forEach(function callback(el) {})bekerja secara alami: Panggilan balik yang dilewati secara alami membentuk ruang lingkup pembungkus dengan el diikat dengan benar dalam setiap iterasi dari forEach. Jadi setiap fungsi dalam yang didefinisikan di callback akan dapat menggunakan hak elnilai
wpding
13

Dengan fitur baru pelingkupan level blok ES6 dikelola:

var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Kode dalam pertanyaan OP diganti dengan letalih - alih var.

Prithvi Uppalapati
sumber
constmemberikan hasil yang sama, dan harus digunakan ketika nilai variabel tidak akan berubah. Namun, penggunaan constdi dalam initializer for for diimplementasikan secara tidak benar di Firefox dan belum diperbaiki. Alih-alih dideklarasikan di dalam blok, ia dideklarasikan di luar blok, yang menghasilkan deklarasi ulang ke variabel, yang pada gilirannya menghasilkan kesalahan. Penggunaan letdi dalam initializer diimplementasikan dengan benar di Firefox, jadi tidak perlu khawatir di sana.
10

Saya terkejut belum ada yang menyarankan menggunakan forEachfungsi untuk lebih baik menghindari (kembali) menggunakan variabel lokal. Sebenarnya, saya tidak menggunakan for(var i ...)sama sekali lagi untuk alasan ini.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// diedit untuk digunakan forEachsebagai ganti peta.

Christian Landgren
sumber
3
.forEach()adalah pilihan yang jauh lebih baik jika Anda tidak benar-benar memetakan apa pun, dan Daryl menyarankan bahwa 7 bulan sebelum Anda memposting, jadi tidak ada yang perlu dikagetkan.
JLRishe
Pertanyaan ini bukan tentang loop over array
jherax
Yah, dia ingin membuat array fungsi, contoh ini menunjukkan bagaimana melakukan itu tanpa melibatkan variabel global.
Christian Landgren
9

Pertanyaan ini benar-benar menunjukkan sejarah JavaScript! Sekarang kita dapat menghindari pelingkupan blok dengan fungsi panah dan menangani loop langsung dari node DOM menggunakan metode Object.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>

sidhuko
sumber
8

Alasan contoh asli Anda tidak berfungsi adalah karena semua penutup yang Anda buat dalam loop mereferensikan frame yang sama. Akibatnya, memiliki 3 metode pada satu objek dengan hanya satu ivariabel. Mereka semua mencetak nilai yang sama.

jottos
sumber
8

Pertama-tama, pahami apa yang salah dengan kode ini:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Di sini ketika funcs[]array sedang diinisialisasi, isedang bertambah, funcsarray diinisialisasi dan ukuran funcarray menjadi 3, jadi i = 3,. Sekarang ketika funcs[j]()dipanggil, itu lagi menggunakan variabel i, yang sudah bertambah menjadi 3.

Sekarang untuk menyelesaikan ini, kami memiliki banyak pilihan. Berikut adalah dua di antaranya:

  1. Kita dapat menginisialisasi idengan letatau menginisialisasi variabel baru indexdengan letdan membuatnya sama dengan i. Jadi ketika panggilan sedang dilakukan, indexakan digunakan dan ruang lingkupnya akan berakhir setelah inisialisasi. Dan untuk panggilan, indexakan diinisialisasi lagi:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
  2. Opsi lain bisa dengan memperkenalkan tempFuncyang mengembalikan fungsi aktual:

    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
Ali Kahoot
sumber
8

Gunakan struktur penutupan , ini akan mengurangi ekstra Anda untuk loop. Anda dapat melakukannya dalam satu loop:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
Vikash Singh
sumber
7

Kami akan memeriksa, apa yang sebenarnya terjadi ketika Anda mendeklarasikan vardan let satu per satu.

Kasus1 : menggunakanvar

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

Sekarang buka jendela konsol chrome Anda dengan menekan F12 dan menyegarkan halaman. Habiskan setiap 3 fungsi di dalam array. Anda akan melihat properti bernama [[Scopes]].Perbesar yang itu. Anda akan melihat satu objek array disebut "Global", perluas itu. Anda akan menemukan properti yang 'i'dideklarasikan ke objek yang memiliki nilai 3.

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Kesimpulan:

  1. Ketika Anda mendeklarasikan variabel menggunakan 'var'fungsi luar, itu menjadi variabel global (Anda dapat memeriksa dengan mengetik iatau window.idi jendela konsol. Ini akan mengembalikan 3).
  2. Fungsi anominous yang Anda nyatakan tidak akan memanggil dan memeriksa nilai di dalam fungsi kecuali jika Anda memanggil fungsi.
  3. Saat Anda memanggil fungsi, console.log("My value: " + i)ambil nilai dari Globalobjeknya dan tampilkan hasilnya.

CASE2: menggunakan let

Sekarang ganti 'var'dengan'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

Lakukan hal yang sama, Pergi ke lingkup. Sekarang Anda akan melihat dua objek "Block"dan "Global". Sekarang rentangkan Blockobjek, Anda akan melihat 'i' didefinisikan di sana, dan yang aneh adalah bahwa, untuk setiap fungsi, nilainya iberbeda (0, 1, 2).

masukkan deskripsi gambar di sini

Kesimpulan:

Ketika Anda mendeklarasikan variabel menggunakan 'let'bahkan di luar fungsi tetapi di dalam loop, variabel ini tidak akan menjadi variabel Global, itu akan menjadi Blockvariabel level yang hanya tersedia untuk fungsi yang sama saja. Itulah alasannya, kami mendapatkan nilai iberbeda untuk setiap fungsi saat kami memanggil fungsi.

Untuk detail lebih lanjut tentang seberapa dekat kerjanya, silakan kunjungi tutorial video yang luar biasa https://youtu.be/71AtaJpJHw0

Bimal Das
sumber
4

Anda bisa menggunakan modul deklaratif untuk daftar data seperti kueri-js (*). Dalam situasi ini saya pribadi menemukan pendekatan deklaratif kurang mengejutkan

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

Anda kemudian dapat menggunakan loop kedua Anda dan mendapatkan hasil yang diharapkan atau bisa Anda lakukan

funcs.iterate(function(f){ f(); });

(*) Saya penulis query-js dan karenanya bias menggunakannya, jadi jangan menganggap kata-kata saya sebagai rekomendasi untuk kata perpustakaan hanya untuk pendekatan deklaratif :)

Rune FS
sumber
1
Saya ingin penjelasan tentang suara turun. Kode memecahkan masalah yang dihadapi. Akan sangat berharga untuk mengetahui bagaimana cara berpotensi meningkatkan kode
Rune FS
1
Apa Query.range(0,3)? Ini bukan bagian dari tag untuk pertanyaan ini. Selain itu, jika Anda menggunakan perpustakaan pihak ketiga, Anda dapat memberikan tautan dokumentasi.
jherax
1
@ Jherax mereka adalah perbaikan yang jelas atau tentu saja. Terima kasih atas komentarnya. Saya berani bersumpah bahwa sudah ada tautan. Dengan keluar bahwa posting itu cukup sia-sia kurasa :). Gagasan awal saya untuk tidak menggunakannya adalah karena saya tidak berusaha mendorong penggunaan perpustakaan saya sendiri tetapi lebih pada ide deklaratif. Namun dalam hinsight saya sepenuhnya setuju bahwa tautannya harus ada di sana
Rune FS
4

Saya lebih suka menggunakan forEachfungsi, yang memiliki penutupan sendiri dengan membuat rentang pseudo:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

Itu terlihat lebih jelek daripada rentang dalam bahasa lain, tetapi IMHO kurang mengerikan dari solusi lain.

Rax Wunter
sumber
Lebih suka apa? Ini sepertinya merupakan komentar sebagai jawaban atas beberapa jawaban lain. Itu sama sekali tidak menjawab pertanyaan yang sebenarnya (karena Anda tidak menugaskan suatu fungsi, untuk dipanggil nanti, di mana saja).
Quentin
Ini terkait persis dengan masalah yang disebutkan: bagaimana cara beralih dengan aman tanpa masalah penutupan
Rax Wunter
Sekarang sepertinya tidak jauh berbeda dari jawaban yang diterima.
Quentin
Tidak. Dalam jawaban yang diterima disarankan untuk menggunakan "beberapa array", tetapi kami berurusan dengan kisaran dalam jawaban, itu hal yang sangat berbeda, yang sayangnya tidak memiliki solusi yang baik dalam js, jadi jawaban saya sedang mencoba untuk menyelesaikan masalah dengan cara yang baik dan praktik
Rax Wunter
@ Quentin Saya akan merekomendasikan untuk menyelidiki solusi sebelum dikurangi
Rax Wunter
4

Dan solusi lain: alih-alih membuat loop lain, hanya mengikat thiske fungsi pengembalian.

var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}

Dengan mengikat ini , menyelesaikan masalah juga.

pixel 67
sumber
3

Banyak solusi tampaknya benar tetapi mereka tidak menyebutkan itu disebut Curryingyang merupakan pola desain pemrograman fungsional untuk situasi seperti di sini. 3-10 kali lebih cepat daripada mengikat tergantung pada browser.

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

Lihat peningkatan kinerja di berbagai browser .

Pawel
sumber
@TinyGiant Contoh dengan fungsi yang dikembalikan masih kari dioptimalkan untuk kinerja. Saya tidak akan melompat pada fungsi ikut-ikutan seperti semua blogger JavaScript. Mereka terlihat keren dan bersih tetapi mempromosikan fungsi penulisan sebaris daripada menggunakan fungsi yang telah ditentukan. Ini bisa menjadi perangkap yang tidak jelas di tempat-tempat panas. Masalah lain adalah bahwa mereka bukan hanya gula sintaksis karena mereka mengeksekusi binding yang tidak perlu sehingga menciptakan penutupan pembungkus.
Pawel
2
Peringatan untuk pembaca di masa mendatang: Jawaban ini tidak akurat menggunakan istilah Currying . "Currying adalah ketika Anda memecah fungsi yang mengambil beberapa argumen menjadi serangkaian fungsi yang mengambil bagian dari argumen." . Kode ini tidak melakukan hal semacam itu. Yang Anda lakukan di sini adalah mengambil kode dari jawaban yang diterima, memindahkan beberapa hal, mengubah gaya dan memberi nama sedikit, lalu menyebutnya kari, yang pasti tidak.
3

Kode Anda tidak berfungsi, karena yang dilakukannya adalah:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

Sekarang pertanyaannya adalah, apa nilai variabel isaat fungsi dipanggil? Karena loop pertama dibuat dengan kondisi i < 3, maka akan berhenti dengan segera ketika kondisinya salah, jadi begitu i = 3.

Anda perlu memahami bahwa, ketika fungsi Anda dibuat, tidak ada satu pun kodenya yang dieksekusi, hanya disimpan untuk nanti. Maka ketika mereka dipanggil kemudian, penerjemah mengeksekusi mereka dan bertanya: "Berapa nilai saat ini i?"

Jadi, tujuan Anda adalah pertama-tama menyimpan nilai ifungsi dan hanya setelah itu simpan fungsi tersebut funcs. Ini bisa dilakukan misalnya dengan cara ini:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Dengan cara ini, setiap fungsi akan memiliki variabel sendiri xdan kami menyetel ini xke nilai idi setiap iterasi.

Ini hanya salah satu dari banyak cara untuk menyelesaikan masalah ini.

Buksy
sumber
3
var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}
Ashish Yadav
sumber
3

Gunakan let (ruang lingkup yang diblokir) alih-alih var.

var funcs = [];
for (let i = 0; i < 3; i++) {      
  funcs[i] = function() {          
    console.log("My value: " + i); 
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      
}

BG Hari Prasad
sumber