Làm cách nào để vượt qua vòng lặp JavaScript?

Khi bạn đính kèm nhiều sự kiện nhấp chuột vào các phần tử, chẳng hạn như các nút bên trong vòng lặp for, sự kiện nhấp chuột sẽ luôn cung cấp cho chúng tôi giá trị chỉ mục cuối cùng bất kể nút nào được nhấn

Show

Đây là một trong những vấn đề phổ biến mà các nhà phát triển gặp phải khi bắt đầu học JavaScript

Đến cuối bài viết này, bạn sẽ biết nguyên nhân gây ra sự cố này và một số cách khắc phục sự cố

Đoạn mã gây ra sự cố

Như bạn có thể thấy, trang HTML có một phần tử

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
0 với một
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
1 được gọi là
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
2




  
  
  JS Bin


  

Đây là nơi tôi đã thêm năm nút một cách linh hoạt bằng cách sử dụng vòng lặp

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
3 trong mã JavaScript bên dưới

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}

Tôi cũng đã đính kèm một sự kiện nhấp chuột vào một phần tử nút và nối nó vào phần tử

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
4 trên mỗi lần lặp lại

Nếu tôi chạy mã ở giai đoạn này, tôi sẽ nhận được giá trị là 5 bất kể nút nào được nhấn

Trước khi hiểu chuyện gì đang xảy ra ở đây… chúng ta cần biết… hoisting là gì

✅Khuyên dùng
Khóa học JavaScript hoàn chỉnh 2020. Xây dựng dự án thực tế

cẩu

Theo mặc định, một biến được khai báo với từ khóa

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
5 là phạm vi chức năng chứ không phải phạm vi khối

Vì vậy, bất kỳ biến nào được khai báo bên trong một hàm, bất kể độ sâu của nó, sẽ được chuyển lên trên cùng và có thể truy cập được ở bất kỳ đâu bên trong hàm đó

Mặt khác, nếu một biến được khai báo bên ngoài một hàm, nó sẽ trở thành một biến có phạm vi toàn cầu và chúng ta có thể truy cập nó ở bất kỳ đâu trong ứng dụng vì nó thuộc về đối tượng cửa sổ (chỉ dành cho trình duyệt)

Hành vi đó được gọi là Hoisting

Biến const buttonsContainer = document.getElementById("buttonsContainer"); for (var i = 0; i < 5; i++) { const button = document.createElement("button"); button.innerText = i; button.addEventListener("click", function() { console.log(i) }) buttonsContainer.appendChild(button); } 6 Luôn Có Chỉ Số Cuối Cùng

Bây giờ hãy xem điều gì xảy ra với đoạn mã trên

Biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 được khai báo với từ khóa
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
5 sẽ tự động được chuyển lên đầu trang vì nó không được khai báo bên trong hàm nên nó trở thành biến toàn cục do hoisting

Vì vậy, biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 rõ ràng không nằm trong phạm vi của vòng lặp
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
3, nhưng nó nằm trong phạm vi toàn cầu và nó bị ràng buộc với cùng một biến bên ngoài hàm gọi lại trên mỗi lần lặp

Vào thời điểm vòng lặp

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
3 đi đến lần lặp cuối cùng, biến
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 sẽ kết thúc việc giữ giá trị chỉ số cuối cùng. Đó là lý do tại sao đầu ra sẽ luôn là chỉ mục cuối cùng, trong trường hợp của tôi là 5

✅Khuyên dùng
JavaScript làm việc với hình ảnh

const buttonsContainer = document.getElementById("buttonsContainer"); for (var i = 0; i < 5; i++) { const button = document.createElement("button"); button.innerText = i; button.addEventListener("click", function() { console.log(i) }) buttonsContainer.appendChild(button); } 6 Là ​​Biến Toàn cục

Tôi sẽ vào console log biến i bên ngoài vòng lặp for

} // end of for loop
console.log(i);

Bạn sẽ nhận được 5 trong bảng điều khiển trình duyệt ngay khi mã kết thúc thực thi mà không cần nhấp vào bất kỳ nút nào

Điều này chứng tỏ rằng biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 có phạm vi toàn cầu

Bây giờ chúng ta đã biết thủ phạm chính là biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 được khai báo với từ khóa
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
5

Hãy xem xét một số giải pháp để khắc phục nó

Giải pháp số 1. Khép kín

Chúng ta có thể sử dụng bao đóng để thay đổi phạm vi của biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 giúp các hàm có thể có biến riêng tư

Sử dụng bao đóng, chúng ta có thể lưu riêng chỉ mục vòng lặp trên mỗi hàm gọi lại

for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);

Hãy xem điều đó trong hành động

Đầu tiên, xác định bao đóng bằng cách sử dụng dấu ngoặc đơn mở và đóng

} // end of for loop
console.log(i);
8 bên trong vòng lặp
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
3

Sau đó, khai báo một hàm ẩn danh nhận tham số chỉ số

Sau đó, chuyển biến toàn cục

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 vào bao đóng với tập cuối cùng của (), gọi bao đóng một lần trên mỗi lần lặp

Điều này còn được gọi là Biểu thức hàm được gọi ngay lập tức (IIFE), đây là một cách để khai báo các bao đóng

(function(){
})()

Vì vậy, đoạn mã trên nắm bắt giá trị của biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 tại mỗi lần lặp lại và chuyển nó thành một đối số cho hàm tạo phạm vi cục bộ

Giờ đây, mỗi hàm có phiên bản riêng của biến chỉ mục sẽ không thay đổi khi các hàm được tạo trong vòng lặp đó

Hàm đóng này bảo toàn giá trị của ___________6 (biến riêng tư) duy nhất cho mỗi trình xử lý sự kiện để mỗi người có quyền truy cập vào giá trị của riêng mình

Khi bạn nhấp vào bất kỳ nút nào sau khi vòng lặp for kết thúc, hàm gọi lại thích hợp sẽ được thực thi với giá trị chỉ mục chính xác

Tôi hy vọng điều đó đúng

✅Khuyên dùng
Khái niệm cơ bản về JavaScript cho người mới bắt đầu

Giải pháp số 2. Đóng chức năng bên ngoài Trả về chức năng bên trong

Ngoài ra, bạn có thể trả về một hàm bên trong hàm gọi lại đóng

    button.addEventListener("click", function(index) {
      return function(){
        console.log(index)
      }
    }(i))

Trong ví dụ trước, toàn bộ mã trình xử lý sự kiện bấm nút được bao bọc bằng bao đóng

Trong ví dụ này, chỉ chức năng gọi lại bấm nút được bao bọc bằng một bao đóng

Hàm bên ngoài sẽ được thực thi trên mỗi lần lặp lại và biến

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
6 (toàn cục) được truyền dưới dạng đối số trong trình gọi hàm bên ngoài như thế này (i)

Hàm bên trong sẽ được trả về trên mỗi lần lặp và được đính kèm với sự kiện nhấp chuột với giá trị chỉ mục duy nhất

Trong bao đóng, các hàm bên trong có thể có quyền truy cập vào các biến được khai báo bên ngoài nó ngay cả sau khi hàm bên ngoài được trả về

Giải pháp số 3. Sử dụng for (var i = 0; i < 5; i++) { var button = document.createElement("button"); button.innerText = i; (function(index){ button.addEventListener("click", function() { console.log(index) }) })(i) buttonsContainer.appendChild(button); } console.log(i);4 Thay vì const buttonsContainer = document.getElementById("buttonsContainer"); for (var i = 0; i < 5; i++) { const button = document.createElement("button"); button.innerText = i; button.addEventListener("click", function() { console.log(i) }) buttonsContainer.appendChild(button); } 3

Điều này sẽ rất tuyệt nếu bạn có một mảng các mục và bạn chỉ cần chạy phương thức

for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);
6 trên đó, nhưng nó không được khuyến nghị khi hoạt động không đồng bộ đang diễn ra trên mỗi lần lặp

Theo mặc định, vòng lặp

for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);
4 cung cấp một cách rõ ràng và tự nhiên để nhận hàm đóng gọi lại riêng biệt trên mỗi lần lặp

const num = [0, 1, 2, 3, 4];

num.forEach(i => {
  var button = document.createElement("button");
  button.innerText = i;
  
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
})

Nó hoạt động mà không cần thêm bất kỳ hàm bao bọc bổ sung nào, sạch hơn ví dụ trước

✅Khuyên dùng
Tóm tắt về Lỗi & Giải pháp CORS

Giải pháp số 4. Sử dụng for (var i = 0; i < 5; i++) { var button = document.createElement("button"); button.innerText = i; (function(index){ button.addEventListener("click", function() { console.log(index) }) })(i) buttonsContainer.appendChild(button); } console.log(i);8 thay vì const buttonsContainer = document.getElementById("buttonsContainer"); for (var i = 0; i < 5; i++) { const button = document.createElement("button"); button.innerText = i; button.addEventListener("click", function() { console.log(i) }) buttonsContainer.appendChild(button); } 5

Trong ES6, chúng tôi có các từ khóa

for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);
8 và
(function(){
})()
1 nằm trong phạm vi khối trái ngược với
const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
5 nằm trong phạm vi chức năng. Nói cách khác,
for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);
8 và
(function(){
})()
1 cũng được nâng lên như var nhưng chúng không được khởi tạo với giá trị mặc định

Vì vậy, sử dụng từ khóa

for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);
8 liên kết hàm gọi lại mới với giá trị chỉ mục trên mỗi lần lặp thay vì sử dụng lặp đi lặp lại cùng một tham chiếu

Để khắc phục điều đó, hãy thay đổi

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
5 thành
for (var i = 0; i < 5; i++) {
  var button = document.createElement("button");
  button.innerText = i;
  (function(index){
    button.addEventListener("click", function() {
      console.log(index)
    })
  })(i)
  buttonsContainer.appendChild(button);

}
console.log(i);
8 từ mã ban đầu và nó hoạt động

for (let i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}

Đây là cách nhanh nhất để khắc phục sự cố sự kiện nhấp chuột trong một vòng lặp

Tuy nhiên, một vấn đề với phương pháp này là phải cẩn thận với khả năng tương thích ngược của trình duyệt vì đây là một phần của tính năng ES6

✅Khuyên dùng
JavaScript. Hiểu các phần kỳ lạ

Khai báo chức năng gọi lại bên ngoài vòng lặp

Đôi khi chúng ta muốn khai báo riêng một hàm gọi lại với một tên thay vì sử dụng một hàm ẩn danh nội tuyến bên trong hàm tạo addEventListener

Vì vậy, hãy khai báo một hàm gọi lại được gọi là hàm buttonClicked() và gọi nó bên trong hàm tạo addEventListener mà không cần bất kỳ dấu ngoặc đơn nào

Theo mặc định, đối tượng

(function(){
})()
8 được truyền vào hàm buttonClicked()

Sau đó, tôi có thể dễ dàng truy cập vào bất kỳ thông tin nào về phần tử đã chọn bằng đối tượng

(function(){
})()
8

for (let i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.id = 'button-' + i;
  button.setAttribute('index', i);
  button.addEventListener("click", buttonClicked)
  buttonsContainer.appendChild(button);
}

function buttonClicked(e) {
  console.log(e.target.id)
  console.log(e.target.getAttribute('index'));
}

Điều gì xảy ra nếu tôi muốn chuyển trực tiếp một giá trị cho hàm gọi lại dưới dạng đối số?

for (let i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.id = 'button-' + i;
  button.setAttribute('index', i);
  button.addEventListener("click", buttonClicked(i))
  buttonsContainer.appendChild(button);
}

function buttonClicked(index) {  
  return function() {
    console.log(index)
  }
}

Điều này rất giống với Giải pháp số 2

Khi chúng ta chuyển một giá trị cho

    button.addEventListener("click", function(index) {
      return function(){
        console.log(index)
      }
    }(i))
0, nó sẽ trở thành một bao đóng (IIFE) là hàm bên ngoài và chạy trên mỗi lần lặp

Hàm bên trong sẽ được trả về trên mỗi lần lặp và được đính kèm với sự kiện nhấp chuột với giá trị chỉ mục

Và… điều đó sẽ làm nên điều kỳ diệu

✅Khuyên dùng
Hướng dẫn JavaScript MVC Sử dụng ES6 – Phần 01

Phần kết luận

Trong bài viết này, bạn đã học cách khắc phục sự cố phát sinh khi bạn đính kèm các sự kiện nhấp chuột bên trong vòng lặp

const buttonsContainer = document.getElementById("buttonsContainer");

for (var i = 0; i < 5; i++) {
  const button = document.createElement("button");
  button.innerText = i;
  button.addEventListener("click", function() {
    console.log(i)
  })
  buttonsContainer.appendChild(button);
}
3 theo một số cách

Phạm vi trong JavaScript là một chủ đề lớn và tôi vẫn đang tìm hiểu về Đóng cửa và các khái niệm khác trong JavaScript

Nếu bạn có bất kỳ đề xuất, phản hồi hoặc nếu bất cứ điều gì không rõ ràng trong bài viết này, vui lòng liên hệ với tôi bằng cách nhận xét bên dưới

Vòng lặp for được thực thi như thế nào trong JavaScript?

Để thực hiện vòng lặp này, điều kiện Bắt đầu được kiểm tra. Đây thường là giá trị ban đầu mà quá trình đếm sẽ bắt đầu. Tiếp theo, Điều kiện được kiểm tra; . Nếu thử nghiệm cho kết quả đúng, thì Biểu thức được sử dụng để sửa đổi vòng lặp và Câu lệnh được thực thi

Làm cách nào để tiếp tục một vòng lặp trong JavaScript?

Câu lệnh continue dùng để bỏ qua bước lặp hiện tại của vòng lặp và luồng điều khiển của chương trình chuyển sang bước lặp tiếp theo. Cú pháp của câu lệnh continue là. tiếp tục [nhãn];

Làm cách nào để thoát khỏi vòng lặp JavaScript?

Từ khóa break cung cấp luồng điều khiển từ câu lệnh switch. Tất cả các loại vòng lặp có thể được đặt bên trong nhau hoặc “lồng nhau. ” Để thoát khỏi các vòng lặp lồng nhau, hãy gắn nhãn cho các vòng lặp và chuyển tên nhãn cho từ khóa ngắt . Điều này hoạt động bất kể có bao nhiêu cấp độ lồng nhau tồn tại.

Làm cách nào để bỏ qua một lần lặp cho vòng lặp JavaScript?

Cú pháp. break tên nhãn; tên nhãn tiếp tục; Chỉ có thể sử dụng câu lệnh continue (có hoặc không có tham chiếu nhãn) để bỏ qua .