การปิด JavaScript ภายในลูป - ตัวอย่างง่ายๆในทางปฏิบัติ


2817

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]();
}

มันเอาท์พุทนี้:

ค่าของฉัน: 3
ค่าของฉัน: 3
ค่าของฉัน: 3

ในขณะที่ฉันต้องการให้มันออก:

ค่าของฉัน: 0
ค่าของฉัน: 1
ค่าของฉัน: 2


ปัญหาเดียวกันนี้เกิดขึ้นเมื่อความล่าช้าในการเรียกใช้ฟังก์ชันเกิดจากการใช้ Event Listeners:

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>

... หรือรหัสอะซิงโครนัสเช่นการใช้สัญญา:

// 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));
}

แก้ไข: นอกจากนี้ยังปรากฏในfor inและfor ofลูป:

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();
}

การแก้ปัญหาพื้นฐานนี้คืออะไร


55
คุณแน่ใจว่าคุณไม่ต้องการfuncsเป็นอาร์เรย์ถ้าคุณใช้ดัชนีตัวเลข? แค่หัวขึ้น
DanMan

23
นี่เป็นปัญหาที่ทำให้สับสนจริงๆ นี้บทความช่วยเหลือฉันในการทำความเข้าใจมัน อาจช่วยคนอื่นด้วย
user3199690

4
อีกวิธีที่ง่ายและอธิบายได้: 1) ฟังก์ชั่นที่ซ้อนกันมีการเข้าถึงขอบเขต "ด้านบน" พวกเขา ; 2) ปิดการแก้ปัญหา ... "ปิดฟังก์ชั่นที่มีการเข้าถึงขอบเขตแม่แม้หลังจากที่ฟังก์ชั่นที่ผู้ปกครองได้ปิด"
Peter Krauss

2
อ้างอิงลิงค์นี้เพื่อดีกว่าjavascript.info/tutorial/advanced-functions
Saurabh Ahuja

35
ในES6การแก้ปัญหาเล็ก ๆ น้อย ๆ คือการประกาศตัวแปรiด้วยการอนุญาตซึ่งจะถูกกำหนดขอบเขตให้กับเนื้อความของลูป
Tomas Nikodym

คำตอบ:


2147

ปัญหาก็คือตัวแปรiภายในแต่ละฟังก์ชั่นนิรนามของคุณถูกผูกไว้กับตัวแปรเดียวกันนอกฟังก์ชั่น

การแก้ปัญหาแบบคลาสสิก: การปิด

สิ่งที่คุณต้องการทำคือผูกตัวแปรภายในแต่ละฟังก์ชั่นกับค่าที่ไม่เปลี่ยนแปลงซึ่งอยู่นอกฟังก์ชั่น:

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]();
}

เนื่องจากไม่มีขอบเขตบล็อกใน JavaScript - เฉพาะขอบเขตฟังก์ชัน - โดยการรวมการสร้างฟังก์ชันในฟังก์ชันใหม่คุณจึงมั่นใจได้ว่าค่าของ "i" ยังคงอยู่ตามที่คุณต้องการ


โซลูชัน ES5.1: forEach

ด้วยความพร้อมใช้งานที่ค่อนข้างแพร่หลายของArray.prototype.forEachฟังก์ชั่น (ในปี 2558) เป็นเรื่องที่น่าสังเกตว่าในสถานการณ์เหล่านั้นที่เกี่ยวข้องกับการวนซ้ำในเรื่องของค่านิยม.forEach()เป็นวิธีที่สะอาดและเป็นธรรมชาติ นั่นคือสมมติว่าคุณมีอาร์เรย์ที่มีค่าบางอย่าง (การอ้างอิง DOM, วัตถุ, อะไรก็ตาม) และปัญหาที่เกิดขึ้นจากการตั้งค่าการเรียกกลับเฉพาะแต่ละองค์ประกอบคุณสามารถทำสิ่งนี้ได้:

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

แนวคิดก็คือว่าการเรียกใช้ฟังก์ชันโทรกลับที่ใช้กับ.forEachลูปแต่ละครั้งจะเป็นการปิดตัวเอง พารามิเตอร์ที่ส่งผ่านไปยังตัวจัดการนั้นเป็นองค์ประกอบอาร์เรย์เฉพาะสำหรับขั้นตอนนั้น ๆ ของการทำซ้ำ ถ้ามันถูกใช้ในการโทรกลับแบบอะซิงโครนัสมันจะไม่ชนกับการเรียกกลับอื่น ๆ ที่สร้างขึ้นในขั้นตอนอื่น ๆ ของการทำซ้ำ

หากคุณกำลังทำงานใน jQuery $.each()ฟังก์ชันจะให้ความสามารถที่คล้ายกัน


โซลูชัน ES6: let

ECMAScript 6 (ES6) แนะนำใหม่letและconstคำหลักที่กำหนดขอบเขตแตกต่างจากvarตัวแปรตาม ตัวอย่างเช่นในลูปที่มีletดัชนีแบบอิงแต่ละการวนซ้ำผ่านลูปจะมีตัวแปรใหม่iพร้อมขอบเขตลูปดังนั้นโค้ดของคุณจะทำงานตามที่คุณคาดหวัง มีทรัพยากรมากมาย แต่ฉันขอแนะนำโพสต์การกำหนดขอบเขตแบบบล็อกของ 2alityเป็นแหล่งข้อมูลที่ยอดเยี่ยม

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

แต่ระวังว่า IE9-IE11 และ Edge ก่อนการสนับสนุน Edge 14 letแต่ผิดพลาดข้างต้น (พวกเขาไม่ได้สร้างใหม่iในแต่ละครั้งดังนั้นฟังก์ชั่นทั้งหมดข้างต้นจะบันทึก 3 อย่างที่พวกเขาต้องการถ้าเราใช้var) ในที่สุด Edge 14 ทำให้ถูกต้องในที่สุด


7
function createfunc(i) { return function() { console.log("My value: " + i); }; }ยังไม่ปิดเพราะใช้ตัวแปรiใช่ไหม
アレックス

55
แต่น่าเสียดายที่คำตอบนี้จะล้าสมัยและไม่มีใครจะเห็นคำตอบที่ถูกต้องที่ด้านล่าง - ใช้Function.bind()เป็นมั่นเหมาะดีกว่าโดยขณะนี้ดูstackoverflow.com/a/19323214/785541
Wladimir Palant

81
@ วลาดิเมียร์: คำแนะนำของคุณ.bind()คือ"คำตอบที่ถูกต้อง"ไม่ถูกต้อง พวกเขาแต่ละคนมีสถานที่ของตัวเอง ด้วย.bind()คุณไม่สามารถผูกอาร์กิวเมนต์โดยไม่ต้องผูกthisค่า นอกจากนี้คุณยังได้รับสำเนาของiอาร์กิวเมนต์โดยไม่มีความสามารถในการกลายพันธุ์ระหว่างการโทรซึ่งบางครั้งจำเป็นต้องใช้ ดังนั้นมันจึงมีโครงสร้างที่แตกต่างกันมากไม่ต้องพูดถึงว่า.bind()การติดตั้งใช้งานช้ากว่าในอดีต แน่นอนว่าในตัวอย่างง่ายๆอาจใช้ได้ แต่การปิดเป็นแนวคิดที่สำคัญที่ต้องเข้าใจและนั่นคือคำถามที่เกี่ยวกับ
มอนสเตอร์คุกกี้

8
โปรดหยุดใช้แฮ็กฟังก์ชัน for-return เหล่านี้ใช้ [] .forEach หรือ [] .map แทนเพราะจะหลีกเลี่ยงการนำตัวแปรขอบเขตเดียวกันมาใช้ซ้ำ
Christian Landgren

32
@ChristianLandgren: มีประโยชน์เฉพาะในกรณีที่คุณทำซ้ำ Array เทคนิคเหล่านี้ไม่ใช่ "แฮ็ก" พวกเขาเป็นความรู้ที่จำเป็น

379

ลอง:

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]();
}

แก้ไข (2014):

โดยส่วนตัวแล้วฉันคิดว่าคำตอบล่าสุด.bindของ @ Aust เกี่ยวกับการใช้เป็นวิธีที่ดีที่สุดในการทำสิ่งนี้ในตอนนี้ นอกจากนี้ยังมีดูเถิดประ / ขีดเป็น_.partialเมื่อคุณไม่ต้องการหรือต้องการที่จะยุ่งกับ'sbindthisArg


2
คำอธิบายใด ๆ เกี่ยวกับ}(i));?
aswzen

3
@ aswzen ฉันคิดว่ามันผ่านiเป็นอาร์กิวเมนต์indexของฟังก์ชั่น
Jet Blue

มันกำลังสร้างดัชนีตัวแปรท้องถิ่น
Abhishek Singh

1
เรียกใช้ฟังก์ชันการแสดงออกทันทีหรือที่รู้จักว่า IIFE (i) คืออาร์กิวเมนต์ของนิพจน์ฟังก์ชันที่ไม่ระบุชื่อที่ถูกเรียกใช้ทันทีและดัชนีจะถูกตั้งค่าจาก i
ไข่

348

อีกวิธีที่ยังไม่ได้กล่าวถึงคือการใช้ 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]();
}

UPDATE

ตามที่ระบุโดย @squint และ @mekdev คุณจะได้รับประสิทธิภาพที่ดีขึ้นโดยการสร้างฟังก์ชั่นภายนอกลูปก่อนแล้วจึงผูกผลลัพธ์ภายในลูป

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]();
}


นี่คือสิ่งที่ฉันทำวันนี้เช่นกันฉันชอบ lo-dash / underscore's_.partial
Bjorn

17
.bind()จะล้าสมัยส่วนใหญ่ด้วยคุณสมบัติ ECMAScript 6 นอกจากนี้จริง ๆ แล้วสร้างสองฟังก์ชันต่อการวนซ้ำ .bind()ครั้งแรกที่ไม่ระบุชื่อแล้วหนึ่งที่สร้างขึ้นโดย การใช้งานที่ดีกว่านั้นก็คือการสร้างมันนอกลูปแล้ว.bind()ก็อยู่ข้างใน

5
@squint @mekdev - คุณทั้งคู่ถูกต้อง ตัวอย่างเริ่มต้นของฉันเขียนขึ้นอย่างรวดเร็วเพื่อสาธิตวิธีการbindใช้งาน ฉันได้เพิ่มอีกตัวอย่างหนึ่งตามคำแนะนำของคุณ
Aust

5
ฉันคิดว่าแทนที่จะเสียการคำนวณมากกว่าสองลูป O (n) เพียงทำเพื่อ (var i = 0; i <3; i ++) {log.call (อันนี้ i); }
2290820

1
.bind () ไม่ว่าคำตอบที่ได้รับการยอมรับให้เห็น PLUS thisไวโอลินกับ
niry

269

การใช้นิพจน์ฟังก์ชันที่เรียกใช้ทันทีวิธีที่ง่ายและอ่านง่ายที่สุดในการล้อมรอบตัวแปรดัชนี:

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);

}

นี้จะส่งการทำซ้ำโดยเข้าสู่ฟังก์ชั่นที่ไม่ระบุชื่อที่เรากำหนดให้เป็นi indexสิ่งนี้จะสร้างการปิดซึ่งตัวแปรiจะถูกบันทึกไว้เพื่อใช้ในภายหลังในการทำงานแบบอะซิงโครนัสภายใน IIFE


10
สำหรับการอ่านรหัสเพิ่มเติมและเพื่อหลีกเลี่ยงความสับสนเป็นที่คือสิ่งที่ฉันต้องการเปลี่ยนชื่อพารามิเตอร์ฟังก์ชั่นi index
Kyle Falconer

5
คุณจะใช้เทคนิคนี้ในการกำหนดอาร์เรย์funcs ที่อธิบายไว้ในคำถามเดิมอย่างไร
Nico

@Nico วิธีเดียวกับที่ปรากฏในคำถามเดิมยกเว้นคุณจะใช้แทนindex 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 ในกรณีเฉพาะของ OP พวกเขาแค่ทำซ้ำตัวเลขดังนั้นมันจึงไม่ใช่กรณีที่ยอดเยี่ยม.forEach()แต่หลายครั้งเมื่อมีคนเริ่มต้นด้วยอาร์เรย์forEach()เป็นตัวเลือกที่ดีเช่น:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
JLRishe

164

สายไปงานปาร์ตี้ แต่ฉันกำลังสำรวจปัญหานี้ในวันนี้และสังเกตเห็นว่าคำตอบจำนวนมากไม่ได้ระบุว่า Javascript ปฏิบัติต่อขอบเขตอย่างสมบูรณ์ซึ่งเป็นสิ่งที่สำคัญนี้

ดังที่คนอื่น ๆ กล่าวถึงปัญหาคือฟังก์ชั่นด้านในอ้างอิงiตัวแปรเดียวกัน ดังนั้นทำไมเราไม่เพียงสร้างตัวแปรท้องถิ่นใหม่แต่ละการวนซ้ำและมีการอ้างอิงฟังก์ชันภายในแทน

//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]();
}

เช่นเดียวกับก่อนที่ฟังก์ชั่นภายในแต่ละเอาท์พุทค่าสุดท้ายที่ได้รับมอบหมายiในขณะนี้ฟังก์ชั่นภายในแต่ละเพียง outputs ilocalค่าสุดท้ายที่ได้รับมอบหมาย แต่ไม่ควรซ้ำกันมีเป็นของตัวเองilocal?

ปรากฎว่าเป็นปัญหา ilocalแต่ละซ้ำแชร์ขอบเขตเดียวกันดังนั้นการทำซ้ำหลังจากที่ครั้งแรกทุกคนเป็นเพียงการเขียนทับ จากMDN :

สำคัญ: JavaScript ไม่มีขอบเขตบล็อก ตัวแปรที่นำมาใช้กับบล็อกนั้นถูกกำหนดขอบเขตให้กับฟังก์ชันหรือสคริปต์ที่มีอยู่และผลกระทบของการตั้งค่าเหล่านั้นยังคงมีอยู่นอกบล็อกนั้น กล่าวอีกนัยหนึ่งคำสั่งบล็อกไม่แนะนำขอบเขต แม้ว่าบล็อก "แบบสแตนด์อโลน" เป็นไวยากรณ์ที่ถูกต้อง แต่คุณไม่ต้องการใช้บล็อกแบบสแตนด์อโลนใน JavaScript เนื่องจากบล็อกดังกล่าวไม่ได้ทำตามที่คุณคิดถ้าหากคุณคิดว่าบล็อกเหล่านั้นทำสิ่งต่าง ๆ เช่นบล็อกใน C หรือ Java

ยังคงเน้นย้ำ:

JavaScript ไม่มีขอบเขตบล็อก ตัวแปรที่นำมาใช้กับบล็อกจะถูกกำหนดขอบเขตไปยังฟังก์ชันหรือสคริปต์ที่มีอยู่

เราสามารถเห็นสิ่งนี้ได้โดยตรวจสอบilocalก่อนที่เราจะประกาศในแต่ละการวนซ้ำ:

//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;
}

นี่คือเหตุผลว่าทำไมข้อผิดพลาดนี้จึงยุ่งยาก แม้ว่าคุณจะประกาศตัวแปรซ้ำ Javascript จะไม่ผิดพลาดและ JSLint จะไม่เตือนเลย นี่คือสาเหตุที่วิธีที่ดีที่สุดในการแก้ปัญหานี้คือการใช้ประโยชน์จากการปิดซึ่งโดยพื้นฐานแล้วเป็นความคิดที่ว่าในจาวาสคริปต์ฟังก์ชั่นด้านในสามารถเข้าถึงตัวแปรด้านนอกได้เพราะขอบเขตด้านใน

ปิด

นี่ก็หมายความว่าฟังก์ชั่นด้านใน "พักไว้ที่" ตัวแปรด้านนอกและทำให้พวกมันมีชีวิตอยู่แม้ว่าฟังก์ชั่นด้านนอกจะกลับมา ในการใช้สิ่งนี้เราสร้างและเรียกใช้ฟังก์ชัน wrapper เพื่อสร้างขอบเขตใหม่ประกาศilocalในขอบเขตใหม่และส่งกลับฟังก์ชันภายในที่ใช้ilocal(คำอธิบายเพิ่มเติมด้านล่าง):

//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]();
}

การสร้างฟังก์ชั่นด้านในภายในฟังก์ชั่น wrapper ช่วยให้ฟังก์ชั่นด้านในมีสภาพแวดล้อมส่วนตัวที่สามารถเข้าถึงได้เพียง "ปิด" ดังนั้นทุกครั้งที่เราเรียกฟังก์ชั่น wrapper เราจะสร้างฟังก์ชั่นด้านในใหม่พร้อมกับสภาพแวดล้อมที่แยกจากกันเพื่อให้แน่ใจว่าilocalตัวแปรจะไม่ชนกันและเขียนทับกัน การเพิ่มประสิทธิภาพเล็กน้อยบางอย่างให้คำตอบสุดท้ายที่ผู้ใช้ SO รายอื่นให้:

//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);
    };
}

ปรับปรุง

ด้วยกระแสหลัก ES6 เราสามารถใช้letคำหลักใหม่เพื่อสร้างตัวแปรที่กำหนดขอบเขตของบล็อก:

//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]();
}

ดูสิว่ามันง่ายแค่ไหน! สำหรับข้อมูลเพิ่มเติมโปรดดูคำตอบนี้ซึ่งข้อมูลของฉันนั้นอิงกับ


ฉันชอบวิธีที่คุณอธิบายวิธี IIFE เช่นกัน ฉันกำลังมองหาสิ่งนั้น ขอบคุณ.
จับภาพทรี

4
ขณะนี้มีสิ่งต่าง ๆ เช่นการบล็อกการกำหนดขอบเขตใน JavaScript โดยใช้letและconstคำหลัก หากคำตอบนี้ขยายออกไปเพื่อรวมมันจะมีประโยชน์มากขึ้นทั่วโลกในความคิดของฉัน

@TinyGiant แน่นอนว่าสิ่งที่ฉันได้เพิ่มข้อมูลเกี่ยวกับletและเชื่อมโยงคำอธิบายที่สมบูรณ์มากขึ้น
woojoo666

@ woojoo666 สามารถคำตอบของคุณยังทำงานสำหรับการโทรทั้งสองสลับ URL ในวงชอบโดย: 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++ }? (สามารถแทนที่ window.open () ด้วย getelementbyid ...... )
nutty about natty

@ nuttyaboutnatty ขอโทษเกี่ยวกับการตอบกลับช้า ดูเหมือนว่ารหัสในตัวอย่างของคุณจะใช้งานไม่ได้ คุณไม่ได้ใช้iในฟังก์ชั่น
ไทม์เอาต์

151

ขณะนี้รองรับ ES6 อย่างกว้างขวางคำตอบที่ดีที่สุดสำหรับคำถามนี้จึงเปลี่ยนไป ES6 จัดทำletและconstคำหลักสำหรับสถานการณ์ที่แน่นอนนี้ แทนที่จะยุ่งกับการปิดเราก็สามารถใช้letเพื่อตั้งค่าตัวแปรขอบเขตลูปดังนี้:

var funcs = [];

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

valจะชี้ไปที่วัตถุที่มีลักษณะเฉพาะกับวงรอบนั้นและจะคืนค่าที่ถูกต้องโดยไม่ต้องมีสัญลักษณ์การปิดเพิ่มเติม เห็นได้ชัดว่าปัญหานี้ง่ายขึ้นอย่างเห็นได้ชัด

constคล้ายletกับข้อ จำกัด เพิ่มเติมที่ชื่อตัวแปรไม่สามารถตอบสนองการอ้างอิงใหม่หลังจากการกำหนดเริ่มต้น

การสนับสนุนเบราว์เซอร์อยู่ที่นี่สำหรับผู้ที่กำหนดเป้าหมายเบราว์เซอร์เวอร์ชันล่าสุด const/ letปัจจุบันรองรับ Firefox, Safari, Edge และ Chrome ล่าสุด นอกจากนี้ยังรองรับ Node และคุณสามารถใช้งานได้ทุกที่โดยใช้ประโยชน์จากเครื่องมือสร้างเช่น Babel คุณสามารถดูตัวอย่างการทำงานได้ที่นี่: http://jsfiddle.net/ben336/rbU4t/2/

เอกสารที่นี่:

แต่ระวังว่า IE9-IE11 และ Edge ก่อนการสนับสนุน Edge 14 letแต่ผิดพลาดข้างต้น (พวกเขาไม่ได้สร้างใหม่iในแต่ละครั้งดังนั้นฟังก์ชั่นทั้งหมดข้างต้นจะบันทึก 3 อย่างที่พวกเขาต้องการถ้าเราใช้var) ในที่สุด Edge 14 ทำให้ถูกต้องในที่สุด


น่าเสียดายที่ "ให้" ยังไม่รองรับอย่างเต็มที่โดยเฉพาะในมือถือ developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......

2
ตั้งแต่วันที่ 16 มิถุนายนเป็นต้นไปให้รองรับเบราว์เซอร์รุ่นใหญ่ทุกรุ่นยกเว้น iOS Safari, Opera Mini และ Safari 9 เบราว์เซอร์ Evergreen รองรับ บาเบลจะ transpile อย่างถูกต้องเพื่อให้พฤติกรรมที่คาดหวังโดยไม่ต้องเปิดโหมด compliancy สูง
Dan Pantry

@DanPantry ใช่เกี่ยวกับเวลาสำหรับการอัปเดต :) อัปเดตเพื่อให้สะท้อนสถานะปัจจุบันของสิ่งต่าง ๆ ได้ดีขึ้นรวมถึงการเพิ่มการกล่าวถึง const, doc doc และข้อมูลความเข้ากันได้ที่ดีขึ้น
Ben McCormick

นี่ไม่ใช่เหตุผลที่เราใช้ babel เพื่อ transpile รหัสของเราเพื่อให้เบราว์เซอร์ที่ไม่รองรับ ES6 / 7 สามารถเข้าใจว่าเกิดอะไรขึ้น
พิกเซล 67

87

อีกวิธีหนึ่งในการบอกว่ามันคือiในฟังก์ชั่นของคุณถูกผูกไว้ในเวลาของการดำเนินการฟังก์ชั่นไม่ใช่เวลาของการสร้างฟังก์ชั่น

เมื่อคุณสร้างการปิดiคือการอ้างอิงถึงตัวแปรที่กำหนดไว้ในขอบเขตภายนอกไม่ใช่สำเนาของมันเหมือนกับเมื่อคุณสร้างการปิด มันจะถูกประเมินในช่วงเวลาของการดำเนินการ

คำตอบอื่น ๆ ส่วนใหญ่มีวิธีการแก้ไขโดยสร้างตัวแปรอื่นที่จะไม่เปลี่ยนค่าสำหรับคุณ

แค่คิดว่าฉันจะเพิ่มคำอธิบายเพื่อความชัดเจน สำหรับวิธีการแก้ปัญหาส่วนตัวฉันจะไปกับฮาร์โต้เพราะมันเป็นวิธีที่อธิบายตนเองได้ดีที่สุดจากคำตอบที่นี่ รหัสใด ๆ ที่โพสต์จะใช้งานได้ แต่ฉันเลือกที่จะปิดโรงงานโดยไม่ต้องเขียนกองความคิดเห็นเพื่ออธิบายว่าทำไมฉันถึงประกาศตัวแปรใหม่ (Freddy and 1800's) หรือมีซินแท็กซ์ปิดฝังแปลก ๆ (apphacker)


71

สิ่งที่คุณต้องเข้าใจคือขอบเขตของตัวแปรในจาวาสคริปต์นั้นขึ้นอยู่กับฟังก์ชั่น นี่คือความแตกต่างที่สำคัญกว่าพูด c # ที่คุณมีขอบเขตบล็อกและเพียงแค่คัดลอกตัวแปรไปยังหนึ่งในสำหรับจะทำงาน

การห่อไว้ในฟังก์ชั่นที่ประเมินการคืนค่าฟังก์ชั่นเช่นคำตอบของ apphacker จะทำการหลอกลวงเนื่องจากตัวแปรมีขอบเขตฟังก์ชั่น

นอกจากนี้ยังมีคำหลัก let แทน var ซึ่งจะอนุญาตให้ใช้กฎขอบเขตบล็อก ในกรณีที่การกำหนดตัวแปรภายในสำหรับจะทำเคล็ดลับ ที่กล่าวว่าคำหลักให้ไม่ได้แก้ปัญหาในทางปฏิบัติเพราะความเข้ากันได้

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]();
}


@nickf เบราว์เซอร์ใด อย่างที่ฉันบอกว่ามันมีปัญหาความเข้ากันได้กับที่ฉันหมายถึงปัญหาความเข้ากันได้อย่างจริงจังเช่นฉันไม่คิดว่าให้การสนับสนุนใน IE
eglasius

1
@nickf ใช่ตรวจสอบข้อมูลอ้างอิงนี้: developer.mozilla.org/En/New_in_JavaScript_1.7 ... ตรวจสอบส่วนคำจำกัดความของการแจ้งให้มีตัวอย่าง onclick ภายในลูป
eglasius

2
@nickf hmm ที่จริงแล้วคุณต้องระบุรุ่นอย่างชัดเจน: <script type = "application / javascript; version = 1.7" /> ... ฉันไม่ได้ใช้จริงทุกที่เพราะข้อ จำกัด IE มันไม่ได้เป็นเพียงแค่ ปฏิบัติ :(
eglasius

คุณสามารถดูการสนับสนุนเบราว์เซอร์สำหรับรุ่นที่แตกต่างกันที่นี่es.wikipedia.org/wiki/Javascript
eglasius


59

นี่คือรูปแบบอื่นของเทคนิคคล้ายกับ Bjorn's (apphacker) ซึ่งช่วยให้คุณกำหนดค่าตัวแปรภายในฟังก์ชั่นแทนที่จะส่งเป็นพารามิเตอร์ซึ่งบางครั้งอาจชัดเจนกว่า:

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

โปรดทราบว่าเทคนิคใดก็ตามที่คุณใช้indexตัวแปรจะกลายเป็นตัวแปรคงที่ซึ่งผูกไว้กับสำเนาที่ส่งคืนของฟังก์ชันภายใน เช่นการเปลี่ยนแปลงค่าจะถูกสงวนไว้ระหว่างการโทร มันมีประโยชน์มาก


ขอขอบคุณและทางออกของคุณใช้งานได้ แต่ฉันอยากจะถามว่าทำไมสิ่งนี้ถึงใช้ได้ แต่การสลับvarสายและreturnสายไม่ทำงาน ขอบคุณ!
midnite

@midnite ถ้าคุณสลับvarและreturnตัวแปรนั้นจะไม่ได้รับมอบหมายก่อนที่จะกลับมาฟังก์ชั่นภายใน
Boann

53

สิ่งนี้อธิบายถึงข้อผิดพลาดทั่วไปเกี่ยวกับการใช้การปิดใน JavaScript

ฟังก์ชั่นกำหนดสภาพแวดล้อมใหม่

พิจารณา:

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

สำหรับแต่ละครั้งที่makeCounterถูกเรียก{counter: 0}ผลลัพธ์ในวัตถุใหม่ที่ถูกสร้างขึ้น นอกจากนี้ยังมีการสร้างสำเนาใหม่obj เพื่ออ้างอิงวัตถุใหม่ ดังนั้นcounter1และcounter2เป็นอิสระจากกัน

ปิดในลูป

การใช้การปิดแบบวนซ้ำนั้นยุ่งยาก

พิจารณา:

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

ขอให้สังเกตว่าcounters[0]และcounters[1]มีความไม่เป็นอิสระ ในความเป็นจริงพวกเขาทำงานเหมือนกันobj!

นี่เป็นเพราะมีเพียงหนึ่งสำเนาของการobjแชร์ในการวนซ้ำทั้งหมดของลูปอาจด้วยเหตุผลด้านประสิทธิภาพ แม้ว่าจะ{counter: 0}สร้างวัตถุใหม่ในการทำซ้ำแต่ละครั้งสำเนาที่เหมือนกันobjจะเพิ่งได้รับการอัปเดตโดยอ้างอิงถึงวัตถุใหม่ล่าสุด

วิธีแก้ไขคือใช้ฟังก์ชั่นตัวช่วยอื่น:

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);
  }
}

สิ่งนี้ใช้ได้เพราะตัวแปรท้องถิ่นในขอบเขตฟังก์ชั่นโดยตรงเช่นเดียวกับตัวแปรอาร์กิวเมนต์ของฟังก์ชั่นจะได้รับการจัดสรรสำเนาใหม่ตามรายการ


ความกระจ่างเล็กน้อย: ในตัวอย่างแรกของการปิดล้อมในลูปตัวนับ [0] และตัวนับ [1] ไม่ได้เป็นอิสระเนื่องจากเหตุผลด้านประสิทธิภาพ เหตุผลคือการ var obj = {counter: 0};ประเมินก่อนที่จะมีการประมวลผลรหัสใด ๆ ตามที่ระบุไว้ใน: การประกาศMDN var : var ไม่ว่าจะเกิดขึ้นที่ไหนจะถูกประมวลผลก่อนที่จะมีการประมวลผลรหัสใด ๆ
Charidimos

50

ทางออกที่ง่ายที่สุดก็คือ

แทนที่จะใช้:

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

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

แจ้งเตือน "2" ครั้งละ 3 ครั้ง นี่เป็นเพราะฟังก์ชั่นที่ไม่ระบุชื่อที่สร้างขึ้นสำหรับการวนรอบการแบ่งปันแบบเดียวกันและในการปิดนั้นค่าของiจะเหมือนกัน ใช้สิ่งนี้เพื่อป้องกันการปิดที่ใช้ร่วมกัน:

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]();
}

ความคิดที่อยู่เบื้องหลังนี้คือห่อหุ้มร่างกายทั้งหมดของห่วงกับIIFE (Expression ฟังก์ชั่นทันที-เรียก) และส่งผ่านเป็นพารามิเตอร์และจับเป็นnew_i iเนื่องจากฟังก์ชั่นที่ไม่ระบุชื่อถูกดำเนินการทันทีiค่าแตกต่างกันไปสำหรับแต่ละฟังก์ชั่นที่กำหนดไว้ภายในฟังก์ชั่นที่ไม่ระบุชื่อ

การแก้ปัญหานี้ดูเหมือนจะเหมาะสมกับปัญหาดังกล่าวเนื่องจากจะต้องมีการเปลี่ยนแปลงเล็กน้อยกับรหัสต้นฉบับที่ทุกข์ทรมานจากปัญหานี้ อันที่จริงแล้วนี่คือการออกแบบโดยที่ไม่ควรมีปัญหาเลย!


2
อ่านสิ่งที่คล้ายกันในหนังสือหนึ่งครั้ง ฉันชอบสิ่งนี้เช่นกันเนื่องจากคุณไม่ต้องแตะรหัสที่มีอยู่ (เท่า) และเห็นได้ชัดว่าทำไมคุณถึงทำเมื่อคุณได้เรียนรู้รูปแบบฟังก์ชั่นการเรียกตนเอง: เพื่อดักจับตัวแปรนั้นในการสร้างใหม่ ขอบเขต.
DanMan

1
@ DanMan ขอบคุณ ฟังก์ชั่นนิรนามการโทรด้วยตนเองเป็นวิธีที่ดีในการจัดการขอบเขตตัวแปรระดับบล็อกของจาวาสคริปต์
Kemal Dağ

3
การเรียกตนเองหรือการเรียกตนเองนั้นไม่ใช่คำที่เหมาะสมสำหรับเทคนิคนี้IIFE (การแสดงออกของฟังก์ชั่นที่เรียกใช้ทันที) นั้นแม่นยำยิ่งขึ้น Ref: benalman.com/news/2010/11/…
jherax

31

ลองอันนี้ที่สั้นกว่านี้

  • ไม่มีอาร์เรย์

  • ไม่มีพิเศษสำหรับการวนซ้ำ


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

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

http://jsfiddle.net/7P6EN/


1
ดูเหมือนว่าทางออกของคุณจะถูกต้อง แต่มันใช้ฟังก์ชั่นโดยไม่จำเป็นทำไมไม่เพียงแค่ console.log ผลลัพธ์? คำถามเดิมเกี่ยวกับการสร้างฟังก์ชั่นที่ไม่ระบุชื่อที่มีการปิดเหมือนกัน ปัญหาคือเนื่องจากพวกเขามีการปิดหนึ่งครั้งค่าของฉันจะเหมือนกันสำหรับแต่ละคน ฉันหวังว่าคุณจะได้รับมัน
Kemal Dağ

30

นี่เป็นวิธีง่ายๆที่ใช้forEach( ใช้งานได้กับ 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
}

พิมพ์:

My value: 0
My value: 1
My value: 2

27

ปัญหาหลักของรหัสที่แสดงโดย OP iคือไม่เคยอ่านจนกว่าจะวนซ้ำครั้งที่สอง ในการสาธิตลองจินตนาการถึงการเห็นข้อผิดพลาดภายในโค้ด

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

ข้อผิดพลาดจริงจะไม่เกิดขึ้นจนกว่าจะถูกดำเนินการfuncs[someIndex] ()การใช้ตรรกะเดียวกันนี้ควรเห็นได้ว่าค่าของiยังไม่ถูกเก็บไว้จนถึงจุดนี้เช่นกัน เมื่อลูปดั้งเดิมเสร็จสิ้นi++จะนำiไปสู่ค่า3ที่ทำให้ผลลัพธ์i < 3ล้มเหลวและสิ้นสุดการวนซ้ำ ณ จุดนี้iคือ3และเมื่อfuncs[someIndex]()มีการใช้และiมีการประเมินมันคือ 3 - ทุกครั้ง

เพื่อให้ได้สิ่งนี้คุณต้องประเมินผลiตามที่พบ โปรดทราบว่าสิ่งนี้ได้เกิดขึ้นแล้วในรูปแบบของfuncs[i](ที่มี 3 ดัชนีที่ไม่ซ้ำกัน) มีหลายวิธีในการเก็บค่านี้ หนึ่งคือการส่งผ่านมันเป็นพารามิเตอร์ไปยังฟังก์ชั่นซึ่งจะแสดงในหลายวิธีแล้วที่นี่

อีกทางเลือกหนึ่งคือการสร้างวัตถุฟังก์ชั่นซึ่งจะสามารถปิดตัวแปร ที่สามารถทำได้อย่างสำเร็จ

jsFiddle Demo

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

23

ฟังก์ชั่นจาวาสคริปต์ "ปิด" ขอบเขตที่พวกเขามีการเข้าถึงตามการประกาศและรักษาการเข้าถึงขอบเขตนั้นแม้ในขณะที่ตัวแปรในการเปลี่ยนแปลงขอบเขตที่

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]()
}

แต่ละฟังก์ชั่นในอาเรย์ข้างต้นปิดเหนือขอบเขตทั่วโลก (ทั่วโลกเพียงเพราะมันเกิดขึ้นกับขอบเขตที่พวกเขาประกาศไว้)

หลังจากนั้นฟังก์ชั่นเหล่านั้นจะถูกเรียกใช้การบันทึกค่าปัจจุบันมากที่สุด iในขอบเขตทั่วโลก นั่นคือความมหัศจรรย์และความยุ่งยากในการปิดตัว

"ฟังก์ชั่นจาวาสคริปต์จะอยู่ใกล้กับขอบเขตที่ประกาศไว้และรักษาสิทธิ์การเข้าถึงขอบเขตนั้นไว้แม้ในขณะที่ค่าตัวแปรภายในขอบเขตการเปลี่ยนแปลงนั้น"

ใช้letแทนการvarแก้ปัญหานี้โดยการสร้างขอบเขตใหม่ในแต่ละครั้งที่forวงวนทำงานสร้างขอบเขตที่แยกออกจากกันเพื่อให้แต่ละฟังก์ชันปิดลง เทคนิคอื่น ๆ อีกมากมายทำสิ่งเดียวกันพร้อมกับฟังก์ชั่นพิเศษ

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]()
}

( letทำให้ตัวแปรบล็อกอยู่ในขอบเขตบล็อกถูกแสดงด้วยเครื่องหมายปีกกา แต่ในกรณีของ for สำหรับลูปตัวแปรการเริ่มต้นiในกรณีของเราถือว่าถูกประกาศในวงเล็บ)


1
ฉันพยายามเข้าใจแนวคิดนี้จนกระทั่งฉันอ่านคำตอบนี้ มันสัมผัสกับจุดสำคัญจริงๆ - มูลค่าของiการตั้งค่าเป็นขอบเขตทั่วโลก เมื่อforลูปเสร็จสิ้นการทำงานค่าโกลบอลของiคือตอนนี้ 3 ดังนั้นเมื่อใดก็ตามที่ฟังก์ชันนั้นถูกเรียกใช้ในอาร์เรย์ (โดยใช้การพูดfuncs[j]) iฟังก์ชันนั้นจะอ้างอิงiตัวแปรส่วนกลาง(ซึ่งคือ 3)
Modermo

13

หลังจากที่ได้อ่านผ่านโซลูชั่นต่างๆที่ผมอยากจะเพิ่มว่าเหตุผลที่ผู้ทำงานการแก้ปัญหาคือการพึ่งพาแนวคิดของห่วงโซ่ขอบเขต เป็นวิธีที่ JavaScript แก้ไขตัวแปรระหว่างการดำเนินการ

  • คำจำกัดความของฟังก์ชันแต่ละตัวจะสร้างขอบเขตซึ่งประกอบด้วยตัวแปรโลคัลทั้งหมดที่ประกาศโดยvarและargumentsและ
  • ถ้าเรามีฟังก์ชั่นภายในที่กำหนดไว้ในฟังก์ชั่นอื่น (ด้านนอก) สิ่งนี้จะก่อให้เกิดห่วงโซ่และจะถูกนำมาใช้ในระหว่างการดำเนินการ
  • เมื่อมีฟังก์ชั่นได้รับการดำเนินรันไทม์ประเมินตัวแปรโดยการค้นหาห่วงโซ่ขอบเขต windowถ้าตัวแปรสามารถพบได้ในบางจุดของห่วงโซ่มันจะหยุดการค้นหาและใช้งานได้มิฉะนั้นต่อไปจนกว่าขอบเขตทั่วโลกถึงซึ่งเป็น

ในรหัสเริ่มต้น:

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

เมื่อได้รับการดำเนินโซ่ขอบเขตจะfuncs function inner -> globalเนื่องจากตัวแปรiไม่สามารถพบได้ในfunction inner(ไม่ประกาศใช้varมิได้ผ่านอาร์กิวเมนต์) ก็ยังคงค้นหาจนค่าของในที่สุดก็พบได้ในขอบเขตทั่วโลกซึ่งเป็นiwindow.i

โดยการห่อไว้ในฟังก์ชั่นด้านนอกอย่างใดอย่างหนึ่งกำหนดฟังก์ชั่นผู้ช่วยเช่นhartoทำหรือใช้ฟังก์ชั่นที่ไม่ระบุชื่อเช่น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

เมื่อได้รับการดำเนินการในขณะนี้ห่วงโซ่ขอบเขตจะfuncs function inner -> function outerเวลานี้iสามารถพบได้ในขอบเขตของฟังก์ชั่นด้านนอกซึ่งดำเนินการ 3 ครั้งใน for for loop แต่ละครั้งมีค่าที่iถูกผูกไว้อย่างถูกต้อง มันจะไม่ใช้ค่าwindow.iเมื่อดำเนินการภายใน

รายละเอียดเพิ่มเติมสามารถพบได้ที่นี่
มันรวมถึงข้อผิดพลาดทั่วไปในการสร้างการปิดในลูปเช่นเดียวกับที่เรามีที่นี่เช่นเดียวกับสาเหตุที่เราต้องปิดและการพิจารณาประสิทธิภาพ


เราไม่ค่อยเขียนตัวอย่างโค้ดนี้จริง แต่ฉันคิดว่ามันเป็นตัวอย่างที่ดีในการทำความเข้าใจพื้นฐาน เมื่อเรามีขอบเขตในใจและวิธีการที่พวกเขาถูกล่ามโซ่ด้วยกันก็เป็นที่ชัดเจนมากขึ้นจะเห็นว่าทำไมวิธีการอื่น ๆ 'ทันสมัยชอบArray.prototype.forEach(function callback(el) {})ธรรมชาติการทำงาน: forEachการเรียกกลับที่ส่งผ่านในรูปแบบธรรมชาติขอบเขตห่อด้วยเอลที่ถูกผูกไว้อย่างถูกต้องในการทำซ้ำของแต่ละ ดังนั้นทุกฟังก์ชั่นภายในที่กำหนดไว้ในการโทรกลับจะสามารถใช้elค่าที่ถูกต้อง
wpding

13

ด้วยคุณสมบัติใหม่ของการกำหนดขอบเขตระดับบล็อก ES6 ได้รับการจัดการ:

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
}

รหัสในคำถามของ OP จะถูกแทนที่ด้วยแทนletvar


constให้ผลลัพธ์เดียวกันและควรใช้เมื่อค่าของตัวแปรไม่เปลี่ยนแปลง อย่างไรก็ตามการใช้งานconstภายใน initializer ของ for loop ถูกนำไปใช้อย่างไม่ถูกต้องใน Firefox และยังไม่ได้รับการแก้ไข แทนที่จะถูกประกาศภายในบล็อกมันถูกประกาศภายนอกบล็อกซึ่งส่งผลให้มีการประกาศซ้ำกับตัวแปรซึ่งจะส่งผลให้เกิดข้อผิดพลาด การใช้letInside initializer นั้นถูกนำไปใช้อย่างถูกต้องใน Firefox ดังนั้นไม่จำเป็นต้องกังวล

10

ฉันแปลกใจที่ยังไม่มีใครแนะนำให้ใช้forEachฟังก์ชั่นนี้เพื่อหลีกเลี่ยงการใช้ตัวแปรท้องถิ่น ที่จริงแล้วฉันไม่ได้ใช้for(var i ...)เลยด้วยเหตุนี้

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

// แก้ไขเพื่อใช้forEachแทนแผนที่


3
.forEach()เป็นตัวเลือกที่ดีกว่าถ้าคุณไม่ได้ทำแผนที่อะไรจริง ๆ และดาริลแนะนำว่า 7 เดือนก่อนที่คุณจะโพสต์ดังนั้นจึงไม่มีอะไรน่าแปลกใจ
JLRishe

คำถามนี้ไม่เกี่ยวกับการวนรอบอาร์เรย์
jherax

ทีนี้เขาต้องการสร้างฟังก์ชั่นมากมายตัวอย่างนี้แสดงวิธีการทำโดยไม่ต้องเกี่ยวข้องกับตัวแปรทั่วโลก
Christian Landgren

9

คำถามนี้แสดงให้เห็นถึงประวัติของ JavaScript จริงๆ! ตอนนี้เราสามารถหลีกเลี่ยงการบล็อกการกำหนดขอบเขตด้วยฟังก์ชันลูกศรและจัดการลูปโดยตรงจากโหนด DOM โดยใช้วิธีการของวัตถุ

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>


8

เหตุผลที่ตัวอย่างดั้งเดิมของคุณไม่ทำงานคือการปิดทั้งหมดที่คุณสร้างในลูปอ้างอิงเฟรมเดียวกัน ในความเป็นจริงมี 3 วิธีในวัตถุหนึ่งเดียวที่มีiตัวแปรเดียว พวกเขาทั้งหมดพิมพ์ค่าเดียวกัน


8

ก่อนอื่นทำความเข้าใจว่ามีอะไรผิดปกติกับรหัสนี้:

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
}

ที่นี่เมื่อfuncs[]อาร์เรย์จะถูกเริ่มต้นiจะถูกเพิ่มขึ้นที่funcsอาร์เรย์จะเริ่มต้นและขนาดของfuncอาร์เรย์จะกลายเป็น 3 i = 3,ดังนั้น ตอนนี้เมื่อfuncs[j]()มีการเรียกมันเป็นอีกครั้งโดยใช้ตัวแปรiซึ่งได้รับการเพิ่มขึ้นถึง 3

ตอนนี้เพื่อแก้ปัญหานี้เรามีตัวเลือกมากมาย ด้านล่างนี้เป็นสองรายการ:

  1. เราสามารถเริ่มต้นiด้วยletหรือเริ่มต้นตัวแปรใหม่indexด้วยและทำให้มันเท่ากับlet iดังนั้นเมื่อมีการโทรindexจะถูกนำมาใช้และขอบเขตของมันจะสิ้นสุดลงหลังจากการเริ่มต้น และสำหรับการโทรindexจะเริ่มต้นได้อีกครั้ง:

    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. ตัวเลือกอื่น ๆ สามารถที่จะแนะนำtempFuncฟังก์ชั่นที่คืนค่าจริง:

    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]();                        
    }

8

ใช้โครงสร้างการปิดซึ่งจะช่วยลดการพิเศษของคุณสำหรับวง คุณสามารถทำได้ด้วยการวนซ้ำ:

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

7

เราจะตรวจสอบสิ่งที่เกิดขึ้นจริงเมื่อคุณประกาศvarและlet หนึ่งโดยหนึ่ง

กรณีที่ 1 : การใช้var

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

ตอนนี้เปิดหน้าต่างคอนโซลของChromeโดยการกดF12และรีเฟรชหน้า ขยายทุก ๆ 3 ฟังก์ชั่นภายในอาร์เรย์คุณจะเห็นคุณสมบัติที่เรียก[[Scopes]]ว่า. คุณจะเห็นวัตถุอาร์เรย์หนึ่งที่เรียกว่า"Global"ขยายวัตถุนั้น คุณจะพบคุณสมบัติที่'i'ประกาศในวัตถุที่มีค่า 3

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

สรุป:

  1. เมื่อคุณประกาศตัวแปรโดยใช้'var'ฟังก์ชั่นนอกมันจะกลายเป็นตัวแปรทั่วโลก (คุณสามารถตรวจสอบได้โดยการพิมพ์iหรือ window.iในหน้าต่างคอนโซลมันจะกลับ 3)
  2. ฟังก์ชั่นเป็นลางสังหรณ์ที่คุณประกาศจะไม่เรียกและตรวจสอบค่าภายในฟังก์ชั่นเว้นแต่ว่าคุณจะเรียกใช้ฟังก์ชั่น
  3. เมื่อคุณเรียกใช้ฟังก์ชันconsole.log("My value: " + i)รับค่าจากGlobalวัตถุและแสดงผลลัพธ์

CASE2: ใช้ let

ตอนนี้แทนที่'var'ด้วย'let'

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

ทำสิ่งเดียวกันไปที่ขอบเขต ตอนนี้คุณจะเห็นสองวัตถุและ"Block" "Global"ตอนนี้ขยายBlockวัตถุคุณจะเห็น 'i' ถูกกำหนดไว้ที่นั่นและสิ่งที่แปลกคือสำหรับทุกฟังก์ชั่นค่าiจะแตกต่างกัน (0, 1, 2)

ป้อนคำอธิบายรูปภาพที่นี่

สรุป:

เมื่อคุณประกาศตัวแปรที่ใช้'let'แม้จะอยู่นอกฟังก์ชั่น แต่ภายในลูปตัวแปรนี้จะไม่เป็นตัวแปรโกลบอลมันจะกลายเป็นBlockตัวแปรระดับที่ใช้ได้เฉพาะกับฟังก์ชั่นเดียวกันเท่านั้นนั่นคือเหตุผลที่เราได้รับค่าที่iแตกต่างกัน สำหรับแต่ละฟังก์ชั่นเมื่อเราเรียกใช้ฟังก์ชั่น

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานที่ใกล้ชิดโปรดอ่านวิดีโอการสอนที่ยอดเยี่ยมhttps://youtu.be/71AtaJpJHw0


4

คุณสามารถใช้โมดูลที่ประกาศสำหรับรายการข้อมูลเช่นquery-js (*) ในสถานการณ์เหล่านี้ฉันพบว่าวิธีการเปิดเผยเป็นการส่วนตัวที่น่าแปลกใจน้อยลง

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

จากนั้นคุณสามารถใช้วงที่สองของคุณและรับผลลัพธ์ที่คาดหวังหรือคุณสามารถทำได้

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

(*) ฉันเป็นผู้เขียนเคียวรี -js และมีอคติต่อการใช้ดังนั้นอย่าใช้คำของฉันเป็นคำแนะนำสำหรับห้องสมุดที่กล่าวถึงเฉพาะสำหรับวิธีการที่เปิดเผย :)


1
ฉันชอบคำอธิบายของการลงคะแนนเสียง รหัสแก้ปัญหาได้ในมือ มันจะมีค่าที่จะทราบวิธีการปรับปรุงโค้ด
Rune FS

1
คือQuery.range(0,3)อะไร นี่ไม่ใช่ส่วนหนึ่งของแท็กสำหรับคำถามนี้ นอกจากนี้หากคุณใช้ห้องสมุดบุคคลที่สามคุณสามารถระบุลิงก์ของเอกสาร
jherax

1
@herax เหล่านั้นหรือหลักสูตรการปรับปรุงที่ชัดเจน ขอบคุณสำหรับความคิดเห็น ฉันสาบานได้ว่ามีลิงก์อยู่แล้ว โดยไม่ต้องโพสต์ที่ไม่มีจุดหมายสวยฉันเดา :) ความคิดเริ่มต้นของฉันในการทำให้มันออกมาเพราะฉันไม่ได้พยายามผลักดันการใช้ห้องสมุดของตัวเอง อย่างไรก็ตามในความเข้าใจฉันเห็นด้วยอย่างยิ่งว่าควรจะมีลิงค์
Rune FS

4

ฉันชอบที่จะใช้forEachฟังก์ชั่นที่มีการปิดของตัวเองด้วยการสร้างช่วงหลอก:

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
}

ที่ดูน่าเกลียดกว่าช่วงในภาษาอื่น ๆ แต่ IMHO มีความผิดพลาดน้อยกว่าโซลูชั่นอื่น ๆ


ต้องการอะไรนะ นี่ดูเหมือนจะเป็นความคิดเห็นในการตอบกลับคำตอบอื่น ๆ ไม่ได้ตอบคำถามจริงเลย (เนื่องจากคุณไม่ได้กำหนดฟังก์ชั่นให้เรียกในภายหลังไม่ว่าที่ใดก็ตาม)
เควนติน

มันเกี่ยวข้องกับประเด็นที่กล่าวถึง: วิธีการวนซ้ำอย่างปลอดภัยโดยไม่มีปัญหาการปิด
Rax Wunter

ตอนนี้ดูเหมือนจะไม่แตกต่างจากคำตอบที่ยอมรับ
เควนติน

ไม่ในคำตอบที่ยอมรับขอแนะนำให้ใช้ "อาร์เรย์บางตัว" แต่เราจัดการกับช่วงของคำตอบมันต่างกันอย่างสิ้นเชิงซึ่งน่าเสียดายที่ไม่มีวิธีแก้ปัญหาที่ดีใน js ดังนั้นคำตอบของฉันจึงพยายามแก้ไข ปัญหาในวิธีที่ดีและการปฏิบัติ
Rax Wunter

@Quentin ฉันขอแนะนำให้ตรวจสอบวิธีแก้ปัญหาก่อนที่จะลดจำนวน
Rax Wunter

4

และวิธีการแก้ปัญหาอื่น: แทนที่จะสร้างวงอื่นเพียงผูกthisฟังก์ชั่นกลับมา

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
}

โดยการผูกสิ่งนี้แก้ปัญหาได้เช่นกัน


3

วิธีแก้ปัญหาหลายอย่างดูเหมือนจะถูกต้อง แต่พวกเขาไม่ได้พูดถึงมันถูกเรียกว่าCurryingเป็นรูปแบบการออกแบบโปรแกรมที่ใช้งานได้สำหรับสถานการณ์เช่นนี้ เร็วกว่าการผูก 3-10 เท่าขึ้นอยู่กับเบราว์เซอร์

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);
  }
}

ดูกำไรจากผลการดำเนินงานในเบราว์เซอร์ที่แตกต่างกัน


@TinyGiant ตัวอย่างที่มีฟังก์ชั่นการส่งคืนยังคงอยู่ในการปรับปรุงที่ดีที่สุดสำหรับประสิทธิภาพการทำงาน ฉันจะไม่กระโดดขึ้นไปบนฟังก์ชั่นลูกศร bandwagon เหมือนบล็อกเกอร์ JavaScript พวกเขาดูดีและสะอาด แต่ส่งเสริมการเขียนฟังก์ชั่นแบบอินไลน์แทนที่จะใช้ฟังก์ชั่นที่กำหนดไว้ล่วงหน้า นี่อาจเป็นกับดักที่ไม่ชัดเจนในที่ร้อน ปัญหาอีกประการหนึ่งคือพวกเขาไม่ได้เป็นเพียงแค่วากยสัมพันธ์น้ำตาลเพราะพวกเขากำลังดำเนินการผูกที่ไม่จำเป็นจึงทำให้การปิดล้อม
Pawel

2
คำเตือนให้กับผู้อ่านในอนาคต:คำตอบนี้ไม่ถูกต้องใช้ระยะความดีความชอบ "การ Currying คือเมื่อคุณแยกฟังก์ชันที่ใช้อาร์กิวเมนต์หลาย ๆ ตัวลงในชุดฟังก์ชันที่มีส่วนร่วมของอาร์กิวเมนต์" . รหัสนี้ไม่มีการจัดเรียง สิ่งที่คุณทำที่นี่คือใช้รหัสจากคำตอบที่ยอมรับย้ายบางสิ่งรอบ ๆ เปลี่ยนสไตล์และตั้งชื่อเล็กน้อยจากนั้นเรียกมันว่าการแก้เผ็ดซึ่งมันไม่มีหมวดหมู่

3

รหัสของคุณใช้งานไม่ได้เพราะมันคืออะไร:

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?

ตอนนี้คำถามคืออะไรค่าของตัวแปรiเมื่อฟังก์ชั่นที่เรียกว่า? เพราะวงแรกที่ถูกสร้างขึ้นด้วยสภาพของจะหยุดทันทีเมื่อเงื่อนไขเป็นเท็จจึงเป็นi < 3i = 3

คุณต้องเข้าใจว่าในเวลาที่ฟังก์ชั่นของคุณถูกสร้างขึ้นไม่มีการประมวลผลโค้ดใด ๆ มันจะถูกบันทึกไว้เพื่อใช้ในภายหลังเท่านั้น เมื่อมีการเรียกใช้ในภายหลังล่ามจะเรียกใช้งานพวกเขาและถามว่า: "ค่าปัจจุบันของiอะไรคืออะไร"

ดังนั้นเป้าหมายของคุณคือครั้งแรกที่บันทึกค่าของฟังก์ชั่นและหลังจากที่บันทึกการทำงานเพื่อi funcsสิ่งนี้สามารถทำได้เช่นนี้:

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
}

ด้วยวิธีนี้แต่ละฟังก์ชั่นจะมีตัวแปรเป็นของตัวเองxและเราตั้งค่านี้xเป็นค่าของiการทำซ้ำแต่ละครั้ง

นี่เป็นวิธีเดียวเท่านั้นในการแก้ปัญหานี้


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
}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.