JavaScript đóng bên trong các vòng lặp - ví dụ thực tế đơn giản


2818

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

Nó xuất ra điều này:

Giá trị của tôi: 3
Giá trị của tôi: 3
Giá trị của tôi: 3

Trong khi tôi muốn nó xuất ra:

Giá trị của tôi: 0
Giá trị của tôi: 1
Giá trị của tôi: 2


Vấn đề tương tự xảy ra khi sự chậm trễ trong việc chạy chức năng là do sử dụng trình lắng nghe sự kiện:

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>

Mã hoặc mã không đồng bộ, ví dụ: sử dụng Promise:

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

Chỉnh sửa: Nó cũng rõ ràng trong for infor ofvòng lặp:

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

Giải pháp cho vấn đề cơ bản này là gì?


55
Bạn có chắc chắn không muốn funcslà một mảng, nếu bạn đang sử dụng các chỉ số số? Chỉ cần một cái đầu lên.
DanMan

23
Đây thực sự là vấn đề khó hiểu. Bài viết này giúp tôi hiểu nó . Nó có thể giúp đỡ người khác quá.
dùng3199690

4
Một giải pháp đơn giản và được giải thích khác: 1) Các hàm lồng nhau có quyền truy cập vào phạm vi "ở trên" chúng ; 2) một giải pháp đóng ... "Đóng là một hàm có quyền truy cập vào phạm vi cha, ngay cả sau khi hàm cha đã đóng".
Peter Krauss

2
Tham khảo liên kết này để hiểu rõ hơn về javascript.info/tutorial/advified-fifts
Saurabh Ahuja

35
Trong ES6 , một giải pháp tầm thường là khai báo biến i với let , nằm trong phạm vi thân của vòng lặp.
Tomas Nikodym

Câu trả lời:


2148

Vấn đề là biến đó i, trong mỗi hàm ẩn danh của bạn, bị ràng buộc với cùng một biến bên ngoài hàm.

Giải pháp cổ điển: Đóng cửa

Những gì bạn muốn làm là liên kết biến trong mỗi hàm với một giá trị riêng, không thay đổi bên ngoài hàm:

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

Vì không có phạm vi khối trong JavaScript - chỉ có phạm vi chức năng - bằng cách gói việc tạo hàm trong một hàm mới, bạn đảm bảo rằng giá trị của "i" vẫn như bạn dự định.


Giải pháp ES5.1: forEach

Với tính khả dụng tương đối rộng rãi của Array.prototype.forEachhàm (năm 2015), đáng chú ý là trong các tình huống liên quan đến phép lặp chủ yếu trên một mảng các giá trị, .forEach()cung cấp một cách rõ ràng, tự nhiên để có được sự đóng cửa riêng biệt cho mỗi lần lặp. Đó là, giả sử bạn đã có một số loại mảng chứa các giá trị (tham chiếu DOM, đối tượng, bất cứ thứ gì) và vấn đề phát sinh khi thiết lập các cuộc gọi lại cụ thể cho từng thành phần, bạn có thể làm điều này:

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

Ý tưởng là mỗi lời gọi của hàm gọi lại được sử dụng với .forEachvòng lặp sẽ được đóng lại riêng. Tham số được truyền vào trình xử lý đó là phần tử mảng cụ thể cho bước lặp cụ thể đó. Nếu nó được sử dụng trong một cuộc gọi lại không đồng bộ, nó sẽ không va chạm với bất kỳ cuộc gọi lại nào khác được thiết lập ở các bước khác của lần lặp.

Nếu bạn tình cờ làm việc trong jQuery, $.each()hàm này cung cấp cho bạn một khả năng tương tự.


Giải pháp ES6: let

ECMAScript 6 (ES6) giới thiệu các từ khóa mới letconstcác từ khóa có phạm vi khác với varcác biến dựa trên. Ví dụ: trong một vòng lặp có letchỉ mục dựa trên cơ sở, mỗi lần lặp qua vòng lặp sẽ có một biến mới ivới phạm vi vòng lặp, do đó mã của bạn sẽ hoạt động như bạn mong đợi. Có rất nhiều tài nguyên, nhưng tôi muốn giới thiệu bài đăng khối của 2ality như một nguồn thông tin tuyệt vời.

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

Mặc dù vậy, hãy cẩn thận, IE9-IE11 và Edge trước khi hỗ trợ Edge 14 letnhưng lại bị lỗi ở trên (chúng không tạo mới imỗi lần, vì vậy tất cả các chức năng trên sẽ đăng nhập 3 như chúng ta sẽ sử dụng var). Edge 14 cuối cùng đã làm cho nó đúng.


7
function createfunc(i) { return function() { console.log("My value: " + i); }; }vẫn không đóng cửa vì nó sử dụng biến i?
ア レ ッ ク ス

55
Thật không may, câu trả lời này đã lỗi thời và không ai sẽ thấy câu trả lời chính xác ở phía dưới - hiện tại việc sử dụng Function.bind()chắc chắn là thích hợp hơn, xem stackoverflow.com/a/19323214/785541 .
Wladimir Palant

81
@Wladimir: Đề xuất của bạn .bind()"câu trả lời đúng" không đúng. Họ đều có vị trí riêng của họ. Với .bind()bạn không thể ràng buộc đối số mà không ràng buộc thisgiá trị. Ngoài ra, bạn nhận được một bản sao của iđối số mà không có khả năng biến đổi nó giữa các cuộc gọi, đôi khi là cần thiết. Vì vậy, chúng có cấu trúc khá khác nhau, chưa kể đến việc .bind()triển khai đã chậm trong lịch sử. Chắc chắn trong ví dụ đơn giản sẽ có hiệu quả, nhưng đóng cửa là một khái niệm quan trọng để hiểu, và đó là những gì câu hỏi là về.
quái vật cookie

8
Vui lòng ngừng sử dụng các hack hàm trả về này, thay vào đó, sử dụng [] .forEach hoặc [] .map vì chúng tránh sử dụng lại các biến phạm vi tương tự.
Christian Landgren 7/2/2015

32
@ChristianLandgren: Điều đó chỉ hữu ích nếu bạn lặp lại một mảng. Những kỹ thuật này không phải là "hack". Chúng là những kiến ​​thức thiết yếu.

379

Thử:

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

Chỉnh sửa (2014):

Cá nhân tôi nghĩ rằng câu trả lời gần đây hơn.bind của @ Aust về việc sử dụng là cách tốt nhất để làm điều này ngay bây giờ. Ngoài ra còn có lo-dash / gạch dưới là _.partialkhi bạn không cần hoặc muốn gây rối với bind's thisArg.


2
bất kỳ lời giải thích về }(i));?
aswzen

3
@aswzen Tôi nghĩ nó chuyển qua ilàm đối số indexcho hàm.
Jet Blue

nó thực sự tạo ra chỉ số biến cục bộ.
Abhishek Singh

1
Gọi ngay biểu thức chức năng, còn gọi là IIFE. (i) là đối số cho biểu thức hàm ẩn danh được gọi ngay lập tức và chỉ mục được đặt từ i.
Trứng

348

Một cách khác chưa được đề cập là sử dụng 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]();
}

CẬP NHẬT

Như được chỉ ra bởi @squint và @mekdev, bạn sẽ có hiệu suất tốt hơn bằng cách tạo hàm bên ngoài vòng lặp trước và sau đó ràng buộc kết quả trong vòng lặp.

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


Đây cũng là những gì tôi làm trong những ngày này, tôi cũng thích lo-dash / gạch dưới_.partial
Bjorn

17
.bind()sẽ bị lỗi thời với các tính năng ECMAScript 6. Bên cạnh đó, điều này thực sự tạo ra hai chức năng cho mỗi lần lặp. Đầu tiên là ẩn danh, sau đó là cái được tạo bởi .bind(). Sử dụng tốt hơn sẽ là tạo nó bên ngoài vòng lặp, sau đó .bind()nó bên trong.

5
@squint @mekdev - Cả hai bạn đều đúng. Ví dụ ban đầu của tôi được viết nhanh chóng để chứng minh cách bindsử dụng. Tôi đã thêm một ví dụ khác theo đề xuất của bạn.
Áo

5
Tôi nghĩ thay vì lãng phí tính toán trên hai vòng lặp O (n), chỉ cần làm cho (var i = 0; i <3; i ++) {log.call (this, i); }
dùng2290820

1
.bind () thực hiện những gì câu trả lời được chấp nhận cho thấy câu đố PLUS với this.
niry

269

Sử dụng một biểu thức hàm được gọi ngay lập tức , cách đơn giản nhất và dễ đọc nhất để kèm theo một biến chỉ mục:

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ều này sẽ gửi iterator ivào hàm ẩn danh mà chúng ta định nghĩa là index. Điều này tạo ra một bao đóng, trong đó biến iđược lưu để sử dụng sau này trong bất kỳ chức năng không đồng bộ nào trong IIFE.


10
Để dễ đọc mã hơn và để tránh nhầm lẫn ilà cái gì, tôi đổi tên tham số hàm thành index.
Kyle Falconer

5
Làm thế nào bạn sẽ sử dụng kỹ thuật này để xác định các funcs mảng được mô tả trong câu hỏi ban đầu?
Nico

@Nico Cách tương tự như trong câu hỏi ban đầu, ngoại trừ bạn sẽ sử dụng indexthay vì 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 Trong trường hợp cụ thể của OP, họ chỉ lặp đi lặp lại qua các con số, vì vậy đây sẽ không phải là trường hợp tuyệt vời .forEach(), nhưng rất nhiều lúc, khi một người bắt đầu với một mảng, forEach()là một lựa chọn tốt, như:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
JLRishe

164

Đến bữa tiệc muộn một chút, nhưng tôi đã khám phá vấn đề này ngày hôm nay và nhận thấy rằng nhiều câu trả lời không giải quyết hoàn toàn cách Javascript xử lý phạm vi, mà về cơ bản là điều này thực hiện.

Vì vậy, như nhiều người khác đã đề cập, vấn đề là hàm bên trong đang tham chiếu cùng một ibiến. Vậy tại sao chúng ta không tạo một biến cục bộ mới mỗi lần lặp và thay vào đó có tham chiếu hàm bên trong?

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

Cũng giống như trước đây, nơi mỗi hàm bên trong xuất ra giá trị cuối cùng được gán cho i, bây giờ mỗi hàm bên trong chỉ xuất ra giá trị cuối cùng được gán ilocal. Nhưng không phải mỗi lần lặp đều có cái riêng ilocal?

Hóa ra, đó là vấn đề. Mỗi lần lặp được chia sẻ cùng một phạm vi, vì vậy mỗi lần lặp sau lần đầu tiên chỉ là ghi đè ilocal. Từ MDN :

Quan trọng: JavaScript không có phạm vi khối. Các biến được giới thiệu với một khối được đặt trong phạm vi hàm hoặc tập lệnh chứa và các hiệu ứng của việc đặt chúng vẫn tồn tại ngoài chính khối đó. Nói cách khác, các câu lệnh chặn không đưa ra một phạm vi. Mặc dù các khối "độc lập" là cú pháp hợp lệ, bạn không muốn sử dụng các khối độc lập trong JavaScript, vì chúng không làm những gì bạn nghĩ chúng làm, nếu bạn nghĩ chúng làm bất cứ điều gì như các khối như vậy trong C hoặc Java.

Lặp lại để nhấn mạnh:

JavaScript không có phạm vi khối. Các biến được giới thiệu với một khối được đặt trong phạm vi hàm hoặc tập lệnh chứa

Chúng ta có thể thấy điều này bằng cách kiểm tra ilocaltrước khi chúng ta khai báo nó trong mỗi lần lặp:

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

Đây chính xác là lý do tại sao lỗi này rất khó khăn. Mặc dù bạn đang khai báo lại một biến, Javascript sẽ không gây ra lỗi và JSLint thậm chí sẽ không đưa ra cảnh báo. Đây cũng là lý do tại sao cách tốt nhất để giải quyết vấn đề này là tận dụng các bao đóng, về cơ bản là ý tưởng rằng trong Javascript, các hàm bên trong có quyền truy cập vào các biến bên ngoài vì phạm vi bên trong "bao quanh" phạm vi bên ngoài.

Đóng cửa

Điều này cũng có nghĩa là các hàm bên trong "giữ" các biến ngoài và giữ cho chúng tồn tại, ngay cả khi hàm ngoài trả về. Để sử dụng điều này, chúng tôi tạo và gọi một hàm bao bọc hoàn toàn để tạo một phạm vi mới, khai báo ilocaltrong phạm vi mới và trả về một hàm bên trong sử dụng ilocal(giải thích thêm bên dướ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++) {
    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]();
}

Tạo chức năng bên trong bên trong chức năng bao bọc cung cấp cho chức năng bên trong một môi trường riêng tư mà chỉ có nó có thể truy cập, "đóng cửa". Do đó, mỗi khi chúng ta gọi hàm bao bọc, chúng ta sẽ tạo một hàm bên trong mới với môi trường riêng biệt của nó, đảm bảo các ilocalbiến không va chạm và ghi đè lên nhau. Một vài tối ưu hóa nhỏ đưa ra câu trả lời cuối cùng mà nhiều người dùng SO khác đưa ra:

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

Cập nhật

Với ES6 bây giờ là chính, bây giờ chúng ta có thể sử dụng lettừ khóa mới để tạo các biến trong phạm vi khối:

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

Nhìn nó dễ quá bây giờ! Để biết thêm thông tin hãy xem câu trả lời này , thông tin của tôi dựa trên.


Tôi thích cách bạn giải thích cách IIFE là tốt. Tôi đã tìm kiếm điều đó. Cảm ơn bạn.
CapturedTree

4
Hiện tại có một thứ như chặn phạm vi trong JavaScript bằng cách sử dụng letconsttừ khóa. Nếu câu trả lời này được mở rộng để bao gồm điều đó, theo ý kiến ​​của tôi nó sẽ hữu ích hơn nhiều trên toàn cầu.

@TinyGiant điều chắc chắn, tôi đã thêm một số thông tin về letvà liên kết một lời giải thích đầy đủ hơn
woojoo666

@ woojoo666 Có thể câu trả lời của bạn cũng hoạt động để gọi hai URL xen kẽ trong một vòng lặp như vậy : 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++ }? (có thể thay thế window.open () bằng getelementbyid ......)
hấp dẫn về sự tự nhiên vào

@nuttyaboutnatty xin lỗi về việc trả lời muộn như vậy. Có vẻ như mã trong ví dụ của bạn đã hoạt động. Bạn không sử dụng icác chức năng hết thời gian của mình, vì vậy bạn không cần phải đóng cửa
woojoo666

151

Với ES6 hiện được hỗ trợ rộng rãi, câu trả lời tốt nhất cho câu hỏi này đã thay đổi. ES6 cung cấp letconsttừ khóa cho trường hợp chính xác này. Thay vì loay hoay với các bao đóng, chúng ta chỉ có thể sử dụng letđể đặt biến phạm vi vòng lặp như thế này:

var funcs = [];

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

valsau đó sẽ trỏ đến một đối tượng dành riêng cho vòng quay cụ thể đó của vòng lặp và sẽ trả về giá trị đúng mà không cần ký hiệu đóng bổ sung. Điều này rõ ràng đơn giản hóa đáng kể vấn đề này.

const tương tự như let với hạn chế bổ sung rằng tên biến không thể bật trở lại tham chiếu mới sau khi gán ban đầu.

Hỗ trợ trình duyệt hiện có tại đây cho những người nhắm mục tiêu các phiên bản trình duyệt mới nhất. const/ lethiện được hỗ trợ trong Firefox, Safari, Edge và Chrome mới nhất. Nó cũng được hỗ trợ trong Node và bạn có thể sử dụng nó ở bất cứ đâu bằng cách tận dụng các công cụ xây dựng như Babel. Bạn có thể xem một ví dụ làm việc ở đây: http://jsfiddle.net/ben336/rbU4t/2/

Tài liệu ở đây:

Mặc dù vậy, hãy cẩn thận, IE9-IE11 và Edge trước khi hỗ trợ Edge 14 letnhưng lại bị lỗi ở trên (chúng không tạo mới imỗi lần, vì vậy tất cả các chức năng trên sẽ đăng nhập 3 như chúng ta sẽ sử dụng var). Edge 14 cuối cùng đã làm cho nó đúng.


Thật không may, 'let' vẫn chưa được hỗ trợ đầy đủ, đặc biệt là trong thiết bị di động. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Khăn

2
Kể từ ngày 16 tháng 6, let được hỗ trợ trong tất cả các phiên bản trình duyệt chính trừ iOS Safari, Opera Mini và Safari 9. Các trình duyệt của Evergreen hỗ trợ nó. Babel sẽ phiên mã nó một cách chính xác để giữ hành vi dự kiến ​​mà không bật chế độ tuân thủ cao.
Dan Pantry

@DanPantry yeah về thời gian cập nhật :) Đã cập nhật để phản ánh tốt hơn trạng thái hiện tại của mọi thứ, bao gồm thêm một đề cập đến const, liên kết doc và thông tin tương thích tốt hơn.
Ben McCormick

Đây không phải là lý do tại sao chúng tôi sử dụng babel để dịch mã của chúng tôi để các trình duyệt không hỗ trợ ES6 / 7 có thể hiểu điều gì đang xảy ra?
pixel 67

87

Một cách khác để nói rằng ichức năng của bạn bị ràng buộc tại thời điểm thực thi chức năng, không phải thời điểm tạo chức năng.

Khi bạn tạo bao đóng, i là một tham chiếu đến biến được xác định trong phạm vi bên ngoài, không phải là bản sao của nó như khi bạn tạo bao đóng. Nó sẽ được đánh giá tại thời điểm thực hiện.

Hầu hết các câu trả lời khác cung cấp các cách để giải quyết bằng cách tạo một biến khác sẽ không thay đổi giá trị cho bạn.

Chỉ cần nghĩ rằng tôi sẽ thêm một lời giải thích cho rõ ràng. Đối với một giải pháp, cá nhân, tôi sẽ đi với Harto vì đó là cách tự giải thích nhất từ ​​các câu trả lời ở đây. Bất kỳ mã nào được đăng sẽ hoạt động, nhưng tôi chọn một nhà máy đóng cửa vì phải viết một đống bình luận để giải thích lý do tại sao tôi khai báo một biến mới (Freddy và 1800) hoặc có cú pháp đóng cửa nhúng kỳ lạ (apphacker).


71

Điều bạn cần hiểu là phạm vi của các biến trong javascript dựa trên hàm. Đây là một sự khác biệt quan trọng hơn so với nói c # nơi bạn có phạm vi khối và chỉ cần sao chép biến vào bên trong for sẽ hoạt động.

Việc gói nó trong một hàm đánh giá việc trả về hàm như câu trả lời của apphacker sẽ thực hiện thủ thuật, vì biến bây giờ có phạm vi hàm.

Ngoài ra còn có một từ khóa let thay vì var, cho phép sử dụng quy tắc phạm vi khối. Trong trường hợp đó, việc xác định một biến bên trong for sẽ thực hiện thủ thuật. Điều đó nói rằng, từ khóa let không phải là một giải pháp thực tế vì tính tương thích.

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 trình duyệt nào? như tôi đã nói, nó có vấn đề tương thích, với ý tôi là vấn đề tương thích nghiêm trọng, như tôi không nghĩ rằng hãy để nó được hỗ trợ trong IE.
eglasius

1
@nickf có, kiểm tra tham chiếu này: developer.mozilla.org/En/New_in_JavaScript_1.7 ... kiểm tra phần cho phép định nghĩa, có một ví dụ onclick trong một vòng lặp
eglasius

2
@nickf hmm, thực ra bạn phải chỉ định rõ ràng phiên bản: <script type = "application / javascript; version = 1.7" /> ... Tôi thực sự không sử dụng nó ở bất cứ đâu vì hạn chế của IE, nó chỉ không thực tế :(
eglasius

bạn có thể thấy hỗ trợ trình duyệt cho các phiên bản khác nhau tại đây es.wikipedia.org/wiki/Javascript
eglasius


59

Đây là một biến thể khác của kỹ thuật, tương tự như Bjorn's (apphacker), cho phép bạn gán giá trị biến trong hàm thay vì chuyển nó dưới dạng tham số, đôi khi có thể rõ ràng hơn:

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

Lưu ý rằng bất kỳ kỹ thuật nào bạn sử dụng, indexbiến sẽ trở thành một loại biến tĩnh, bị ràng buộc với bản sao được trả về của hàm bên trong. Tức là, thay đổi giá trị của nó được bảo tồn giữa các cuộc gọi. Nó có thể rất tiện dụng.


Cảm ơn và giải pháp của bạn hoạt động. Nhưng tôi muốn hỏi tại sao điều này hoạt động, nhưng hoán đổi vardòng và returndòng sẽ không hoạt động? Cảm ơn!
midnite

@midnite Nếu bạn hoán đổi varreturnsau đó biến sẽ không được chỉ định trước khi nó trả về hàm bên trong.
Boann

53

Điều này mô tả lỗi phổ biến với việc sử dụng các bao đóng trong JavaScript.

Một hàm xác định một môi trường mới

Xem xét:

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

Đối với mỗi lần makeCounterđược gọi, {counter: 0}kết quả là một đối tượng mới được tạo. Ngoài ra, một bản sao mới obj cũng được tạo để tham chiếu đối tượng mới. Do đó, counter1counter2 độc lập với nhau.

Đóng trong các vòng

Sử dụng một đóng trong một vòng lặp là khó khăn.

Xem xét:

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

Chú ý rằng counters[0]counters[1]không độc lập. Trong thực tế, họ hoạt động như nhau obj!

Điều này là do chỉ có một bản sao objđược chia sẻ trên tất cả các lần lặp của vòng lặp, có lẽ vì lý do hiệu suất. Mặc dù {counter: 0}tạo ra một đối tượng mới trong mỗi lần lặp, cùng một bản sao objsẽ chỉ được cập nhật với một tham chiếu đến đối tượng mới nhất.

Giải pháp là sử dụng chức năng trợ giúp khác:

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

Điều này hoạt động vì các biến cục bộ trong phạm vi hàm trực tiếp, cũng như các biến đối số hàm, được phân bổ các bản sao mới khi nhập.


Làm rõ nhỏ: Trong ví dụ đầu tiên về Đóng cửa trong các vòng lặp, bộ đếm [0] và bộ đếm [1] không độc lập không phải vì lý do hiệu suất. Lý do là var obj = {counter: 0};được đánh giá trước khi bất kỳ mã nào được thực thi như đã nêu trong: khai báo MDN var : var, bất cứ nơi nào chúng xảy ra, được xử lý trước khi bất kỳ mã nào được thực thi.
Charidimos

50

Giải pháp đơn giản nhất là

Thay vì sử dụng:

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

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

thông báo "2", trong 3 lần. Điều này là do các hàm ẩn danh được tạo trong vòng lặp, chia sẻ cùng một bao đóng và trong bao đóng đó, giá trị của ilà như nhau. Sử dụng điều này để ngăn chặn việc đóng cửa chung:

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

Ý tưởng đằng sau điều này là, gói gọn toàn bộ phần thân của vòng lặp for bằng IIFE (Biểu thức hàm được gọi ngay lập tức) và truyền new_idưới dạng tham số và bắt nó như một tham số i. Vì hàm ẩn danh được thực thi ngay lập tức, nên igiá trị sẽ khác nhau đối với mỗi hàm được xác định bên trong hàm ẩn danh.

Giải pháp này dường như phù hợp với bất kỳ vấn đề nào như vậy vì nó sẽ yêu cầu thay đổi tối thiểu đối với mã gốc chịu sự cố này. Trên thực tế, đây là do thiết kế, nó hoàn toàn không phải là một vấn đề!


2
Đọc một cái gì đó tương tự trong một cuốn sách một lần. Tôi cũng thích điều này, vì bạn không phải chạm vào mã hiện tại của mình (càng nhiều) và rõ ràng là tại sao bạn lại làm như vậy, một khi bạn đã học được mẫu hàm tự gọi: để bẫy biến đó trong mã mới được tạo phạm vi.
DanMan

1
@DanMan Cảm ơn. Tự gọi các hàm ẩn danh là cách rất tốt để giải quyết việc thiếu phạm vi biến cấp độ khối của javascript.
Kemal Dağ 26/07/13

3
Tự gọi, hoặc tự gọi không phải là thuật ngữ thích hợp cho kỹ thuật này, IIFE (Biểu hiện chức năng được gọi ngay lập tức) chính xác hơn. Tham chiếu: benalman.com/news/2010/11/ trên
jherax

31

thử cái này ngắn hơn

  • không có mảng

  • không có thêm cho vòng lặp


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

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

http://jsfiddle.net/7P6EN/


1
Giải pháp của bạn có vẻ đầu ra đúng nhưng nó không cần thiết sử dụng các chức năng, tại sao không chỉ console.log đầu ra? Câu hỏi ban đầu là về việc tạo ra các hàm ẩn danh có cùng cách đóng. Vấn đề là, vì chúng có một lần đóng duy nhất, giá trị của i là giống nhau cho mỗi cái. Tôi hy vọng bạn đã nhận nó.
Kemal Dağ

30

Đây là một giải pháp đơn giản sử dụng forEach(hoạt động trở lại 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
}

Bản in:

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

27

Vấn đề chính với mã được hiển thị bởi OP ilà không bao giờ được đọc cho đến vòng lặp thứ hai. Để chứng minh, hãy tưởng tượng nhìn thấy một lỗi bên trong mã

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

Lỗi thực sự không xảy ra cho đến khi funcs[someIndex]được thực thi (). Sử dụng cùng logic này, rõ ràng giá trị của icũng không được thu thập cho đến thời điểm này. Khi vòng lặp ban đầu kết thúc, i++đưa iđến giá trị 3dẫn đến tình trạng i < 3không thành công và vòng lặp kết thúc. Tại thời điểm này, i3và vì thế khi funcs[someIndex]()được sử dụng, và iđược đánh giá, đó là 3 - mỗi lần.

Để vượt qua điều này, bạn phải đánh giá inhư nó gặp phải. Lưu ý rằng điều này đã xảy ra ở dạng funcs[i](trong đó có 3 chỉ mục duy nhất). Có một số cách để nắm bắt giá trị này. Một là truyền nó dưới dạng tham số cho hàm được hiển thị theo nhiều cách đã có ở đây.

Một tùy chọn khác là xây dựng một đối tượng hàm có thể đóng trên biến. Điều đó có thể được thực hiện như vậy

jsFiddle Demo

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

23

Các hàm JavaScript "đóng" phạm vi mà chúng có quyền truy cập khi khai báo và giữ quyền truy cập vào phạm vi đó ngay cả khi các biến trong phạm vi đó thay đổi.

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

Mỗi hàm trong mảng trên đóng trên phạm vi toàn cầu (toàn cục, đơn giản vì đó là phạm vi chúng được khai báo).

Sau đó, các hàm này được gọi đăng nhập giá trị mới nhất itrong phạm vi toàn cầu. Đó là phép màu, và sự thất vọng, của sự đóng cửa.

"Các hàm JavaScript đóng trên phạm vi mà chúng được khai báo và giữ quyền truy cập vào phạm vi đó ngay cả khi các giá trị biến trong phạm vi đó thay đổi."

Sử dụng letthay vì vargiải quyết điều này bằng cách tạo một phạm vi mới mỗi khi forvòng lặp chạy, tạo một phạm vi riêng biệt cho mỗi chức năng để đóng. Nhiều kỹ thuật khác làm điều tương tự với các chức năng bổ sung.

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

( letBiến làm cho chặn scoped. Blocks được biểu thị bằng dấu ngoặc nhọn, nhưng trong trường hợp của vòng lặp for biến khởi tạo, itrong trường hợp của chúng tôi, được coi là tuyên bố trong niềng răng.)


1
Tôi đấu tranh để hiểu khái niệm này cho đến khi tôi đọc câu trả lời này. Nó chạm vào một điểm thực sự quan trọng - giá trị của iđang được đặt thành phạm vi toàn cầu. Khi forvòng lặp kết thúc chạy, giá trị toàn cầu ilà 3. Do đó, bất cứ khi nào hàm đó được gọi trong mảng (sử dụng, giả sử funcs[j]), ihàm đó sẽ tham chiếu ibiến toàn cục (là 3).
Modermo

13

Sau khi đọc qua các giải pháp khác nhau, tôi muốn nói thêm rằng lý do các giải pháp đó hoạt động là dựa vào khái niệm chuỗi phạm vi . Đó là cách JavaScript giải quyết một biến trong khi thực thi.

  • Mỗi định nghĩa hàm tạo thành một phạm vi bao gồm tất cả các biến cục bộ được khai báo bởi varvà của nóarguments .
  • Nếu chúng ta có chức năng bên trong được xác định bên trong chức năng (bên ngoài) khác, điều này tạo thành một chuỗi và sẽ được sử dụng trong khi thực hiện
  • Khi một hàm được thực thi, bộ thực thi đánh giá các biến bằng cách tìm kiếm chuỗi phạm vi . Nếu một biến có thể được tìm thấy ở một điểm nhất định của chuỗi, nó sẽ ngừng tìm kiếm và sử dụng nó, nếu không nó sẽ tiếp tục cho đến khi phạm vi toàn cầu đạt được window.

Trong mã ban đầu:

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

Khi funcsđược thực thi, chuỗi phạm vi sẽ là function inner -> global. Vì biến ikhông thể được tìm thấy trong function inner(không được khai báo sử dụng varcũng không được chuyển làm đối số), nên nó tiếp tục tìm kiếm, cho đến khi giá trị icuối cùng được tìm thấy trong phạm vi toàn cầu window.i.

Bằng cách gói nó trong một chức năng bên ngoài, hoặc xác định rõ ràng một chức năng của trình trợ giúp như harto đã làm hoặc sử dụng một chức năng ẩn danh như Bjorn đã làm:

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

Khi funcsđược thực thi, bây giờ chuỗi phạm vi sẽ là function inner -> function outer. Thời gian này icó thể được tìm thấy trong phạm vi của hàm ngoài được thực hiện 3 lần trong vòng lặp for, mỗi lần có giá trị được iràng buộc chính xác. Nó sẽ không sử dụng giá trị window.ikhi thực hiện bên trong.

Chi tiết hơn có thể được tìm thấy ở đây
Nó bao gồm lỗi phổ biến trong việc tạo đóng trong vòng lặp như những gì chúng ta có ở đây, cũng như lý do tại sao chúng ta cần đóng và xem xét hiệu suất.


Chúng tôi hiếm khi viết mẫu mã này trong thực tế, nhưng tôi nghĩ rằng nó phục vụ một ví dụ tốt để hiểu cơ bản. Khi chúng ta có phạm vi trong tâm trí và cách chúng kết nối với nhau, sẽ rõ hơn để hiểu tại sao các cách 'hiện đại' khác như Array.prototype.forEach(function callback(el) {})hoạt động tự nhiên: Cuộc gọi lại được truyền trong hình thành một cách tự nhiên với phạm vi bao bọc chính xác trong mỗi lần lặp forEach. Vì vậy, mọi hàm bên trong được xác định trong cuộc gọi lại sẽ có thể sử dụng đúng elgiá trị
wpding

13

Với các tính năng mới của phạm vi cấp độ khối ES6 được quản lý:

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
}

Mã trong câu hỏi của OP được thay thế bằng letthay vì var.


constcung cấp cùng một kết quả và nên được sử dụng khi giá trị của biến sẽ không thay đổi. Tuy nhiên, việc sử dụng constbên trong trình khởi tạo của vòng lặp for được triển khai không chính xác trong Firefox và vẫn chưa được sửa. Thay vì được khai báo bên trong khối, nó được khai báo bên ngoài khối, dẫn đến việc khai báo lại biến, từ đó dẫn đến lỗi. Việc sử dụng letbên trong trình khởi tạo được triển khai chính xác trong Firefox, vì vậy không cần phải lo lắng ở đó.

10

Tôi ngạc nhiên chưa có ai đề nghị sử dụng forEachhàm để tránh (tái) sử dụng các biến cục bộ. Trên thực tế, tôi không sử dụng for(var i ...)nữa vì lý do này.

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

// chỉnh sửa để sử dụng forEachthay vì bản đồ.


3
.forEach()là một lựa chọn tốt hơn nhiều nếu bạn không thực sự lập bản đồ bất cứ điều gì và Daryl đề nghị rằng 7 tháng trước khi bạn đăng, vì vậy không có gì phải ngạc nhiên.
JLRishe

Câu hỏi này không phải là về vòng lặp trên một mảng
jherax

Chà, anh ta muốn tạo ra một loạt các hàm, ví dụ này cho thấy cách làm điều đó mà không liên quan đến một biến toàn cục.
Christian Landgren

9

Câu hỏi này thực sự cho thấy lịch sử của JavaScript! Bây giờ chúng ta có thể tránh phạm vi khối với các hàm mũi tên và xử lý các vòng lặp trực tiếp từ các nút DOM bằng các phương thức 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>


8

Lý do ví dụ ban đầu của bạn không hoạt động là tất cả các bao đóng bạn đã tạo trong vòng lặp tham chiếu cùng một khung. Trong thực tế, có 3 phương thức trên một đối tượng chỉ với một ibiến duy nhất . Tất cả đều in ra cùng một giá trị.


8

Trước hết, hãy hiểu những gì sai với mã này:

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
}

Ở đây khi funcs[]mảng đang được khởi tạo, iđang được tăng lên, funcsmảng được khởi tạo và kích thước của funcmảng trở thành 3, vì vậy i = 3,. Bây giờ khi funcs[j]()được gọi, nó lại sử dụng biếni đã được tăng lên 3.

Bây giờ để giải quyết điều này, chúng tôi có nhiều lựa chọn. Dưới đây là hai trong số họ:

  1. Chúng ta có thể khởi tạo ivới lethoặc khởi tạo một biến mới indexvới letvà làm cho nó bằng i. Vì vậy, khi cuộc gọi được thực hiện, indexsẽ được sử dụng và phạm vi của nó sẽ kết thúc sau khi khởi tạo. Và để gọi, indexsẽ được khởi tạo lại:

    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. Tùy chọn khác có thể là để giới thiệu một hàm tempFunctrả về hàm thực tế:

    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

Sử dụng cấu trúc đóng , điều này sẽ làm giảm vòng lặp thêm của bạn. Bạn có thể làm điều đó trong một vòng lặp for:

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

7

Chúng tôi sẽ kiểm tra, những gì thực sự xảy ra khi bạn khai báo varlet từng cái một.

Trường hợp 1 : sử dụngvar

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

Bây giờ hãy mở cửa sổ giao diện điều khiển chrome của bạn bằng cách nhấn F12 và làm mới trang. Chi tiêu cho mỗi 3 hàm bên trong mảng. Bạn sẽ thấy một thuộc tính có tên [[Scopes]].Expand cái đó. Bạn sẽ thấy một đối tượng mảng được gọi "Global", mở rộng đối tượng đó. Bạn sẽ tìm thấy một thuộc tính được 'i'khai báo vào đối tượng có giá trị 3.

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Phần kết luận:

  1. Khi bạn khai báo một biến bằng cách sử dụng 'var'bên ngoài một hàm, nó sẽ trở thành biến toàn cục (bạn có thể kiểm tra bằng cách gõ ihoặc window.itrong cửa sổ giao diện điều khiển. Nó sẽ trả về 3).
  2. Hàm đáng chú ý mà bạn đã khai báo sẽ không gọi và kiểm tra giá trị bên trong hàm trừ khi bạn gọi các hàm.
  3. Khi bạn gọi hàm, console.log("My value: " + i)lấy giá trị từ Globalđối tượng của nó và hiển thị kết quả.

CASE2: sử dụng let

Bây giờ thay thế 'var'bằng'let'

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

Làm điều tương tự, Đi đến phạm vi. Bây giờ bạn sẽ thấy hai đối tượng "Block""Global". Bây giờ mở rộng Blockđối tượng, bạn sẽ thấy 'i' được xác định ở đó và điều kỳ lạ là, đối với mọi hàm, giá trị if ilà khác nhau (0, 1, 2).

nhập mô tả hình ảnh ở đây

Phần kết luận:

Khi bạn khai báo biến bằng cách sử dụng 'let'ngay cả bên ngoài hàm nhưng bên trong vòng lặp, biến này sẽ không phải là biến toàn cục, nó sẽ trở thành Blockbiến cấp chỉ có sẵn cho cùng một hàm. Đó là lý do, chúng tôi đang nhận được giá trị ikhác nhau cho mỗi chức năng khi chúng ta gọi các chức năng.

Để biết thêm chi tiết về cách hoạt động gần hơn, vui lòng xem qua video hướng dẫn tuyệt vời https://youtu.be/71AtaJpJHw0


4

Bạn có thể sử dụng một mô-đun khai báo cho danh sách dữ liệu, chẳng hạn như query-js (*). Trong những tình huống này, cá nhân tôi thấy một cách tiếp cận khai báo ít gây ngạc nhiên hơn

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

Sau đó, bạn có thể sử dụng vòng lặp thứ hai của mình và nhận được kết quả mong đợi hoặc bạn có thể làm

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

(*) Tôi là tác giả của truy vấn-js và vì thiên vị đối với việc sử dụng nó, vì vậy đừng lấy từ ngữ của tôi làm đề xuất cho thư viện nói chỉ dành cho phương pháp khai báo :)


1
Tôi rất thích một lời giải thích về việc bỏ phiếu xuống. Các mã giải quyết vấn đề trong tầm tay. Sẽ rất có giá trị khi biết cách cải thiện mã
Rune FS

1
Query.range(0,3)gì Đây không phải là một phần của các thẻ cho câu hỏi này. Ngoài ra, nếu bạn sử dụng thư viện của bên thứ ba, bạn có thể cung cấp liên kết của tài liệu.
jherax

1
@jherax đó là hoặc khóa học cải tiến rõ ràng. Cảm ơn các bình luận. Tôi có thể đã thề rằng đã có một liên kết. Tôi đoán rằng bài viết khá vô nghĩa :). Ý tưởng ban đầu của tôi về việc giữ nó là vì tôi đã không cố gắng sử dụng thư viện của riêng mình mà là ý tưởng khai báo. Tuy nhiên, trong hoàn cảnh khó khăn, tôi hoàn toàn đồng ý rằng liên kết sẽ ở đó
Rune FS

4

Tôi thích sử dụng forEachchức năng, có chức năng đóng riêng với việc tạo phạm vi giả:

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
}

Điều đó trông xấu hơn phạm vi trong các ngôn ngữ khác, nhưng IMHO ít quái dị hơn các giải pháp khác.


Thích nó để làm gì? Đây dường như là một bình luận để trả lời một số câu trả lời khác. Nó hoàn toàn không giải quyết câu hỏi thực tế (vì bạn không gán chức năng, sẽ được gọi sau, ở bất cứ đâu).
Quentin

Nó liên quan chính xác đến vấn đề được đề cập: làm thế nào để lặp lại an toàn mà không gặp sự cố đóng cửa
Rax Wunter

Bây giờ nó không có vẻ khác biệt đáng kể so với câu trả lời được chấp nhận.
Quentin

Không. Trong câu trả lời được chấp nhận, bạn nên sử dụng "một số mảng", nhưng chúng tôi xử lý một phạm vi trong câu trả lời, đó là những điều hoàn toàn khác nhau, rất tiếc là không có giải pháp tốt trong js, vì vậy câu trả lời của tôi là cố gắng giải quyết vấn đề theo cách tốt và thực hành
Rax Wunter

@Quentin Tôi khuyên bạn nên điều tra giải pháp trước khi sử dụng
Rax Wunter

4

Và một giải pháp khác: thay vì tạo một vòng lặp khác, chỉ cần liên kết thishàm trả về.

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
}

Bằng cách ràng buộc điều này , giải quyết vấn đề là tốt.


3

Nhiều giải pháp có vẻ đúng nhưng họ không đề cập đến nó được gọi Curryinglà mẫu thiết kế lập trình chức năng cho các tình huống như ở đây. Nhanh hơn 3-10 lần so với liên kết tùy thuộc vào trình duyệt.

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

Xem mức tăng hiệu suất trong các trình duyệt khác nhau .


@TinyGiant Ví dụ với chức năng được trả về vẫn đang được tối ưu hóa cho hiệu suất. Tôi sẽ không nhảy vào chức năng mũi tên bandwagon như tất cả các blogger JavaScript. Chúng trông mát mẻ và sạch sẽ nhưng thúc đẩy các chức năng viết nội tuyến thay vì sử dụng các chức năng được xác định trước. Đây có thể là một cái bẫy không rõ ràng ở những nơi nóng. Một vấn đề khác là chúng không chỉ là đường cú pháp bởi vì chúng đang thực hiện các ràng buộc không cần thiết do đó tạo ra sự đóng gói.
Pawel

2
Cảnh báo cho độc giả tương lai: Câu trả lời này áp dụng không chính xác thuật ngữ Currying . "Currying là khi bạn chia nhỏ một hàm đưa nhiều đối số thành một chuỗi các hàm tham gia vào các đối số." . Mã này không có gì của loại. Tất cả những gì bạn đã làm ở đây là lấy mã từ câu trả lời được chấp nhận, di chuyển một số thứ xung quanh, thay đổi kiểu dáng và đặt tên một chút, sau đó gọi nó là currying, mà nó không được phân loại.

3

Mã của bạn không hoạt động, bởi vì những gì nó làm là:

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?

Bây giờ câu hỏi là, giá trị của biến ikhi hàm được gọi là gì? Bởi vì vòng lặp đầu tiên được tạo với điều kiện là i < 3, nó dừng ngay lập tức khi điều kiện sai, vì vậy nó là i = 3.

Bạn cần hiểu rằng, trong thời gian khi các hàm của bạn được tạo, không có mã nào của chúng được thực thi, nó chỉ được lưu cho lần sau. Và khi họ được gọi sau, trình thông dịch sẽ thực thi chúng và hỏi: "Giá trị hiện tại là igì?"

Vì vậy, mục tiêu của bạn trước tiên là lưu giá trị của ihàm và chỉ sau đó lưu hàm đó vào funcs. Điều này có thể được thực hiện ví dụ theo cách này:

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
}

Theo cách này, mỗi hàm sẽ có biến riêng xvà chúng ta đặt xgiá trị này thành giá trị của imỗi lần lặp.

Đây chỉ là một trong nhiều cách để giải quyết vấn đề này.


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
}

3

Sử dụng let (phạm vi bị chặn) thay vì 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]();                      
}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.