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
Đâ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ướiconst 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ạiNế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ốiVì 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
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];
}
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 hoistingVì 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ặpVà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
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 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ầuBâ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];
}
5Hã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];
}
3Sau đó, 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
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];
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];
}
Đ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ặpTheo 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ặpconst 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
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];
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];
}
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 địnhVì 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 độngfor [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[]{
}][]
8for [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ặpHà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áchPhạ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