0

javascript:void(0);: Khi thủ thuật "cứu cánh" ngày xưa trở thành "tội đồ" của hiện tại

Chào anh em Viblo, lại là mình đây!

Nếu anh em nào đã từng trải qua cái thời kỳ "nguyên thủy" của Frontend, cái thời mà jQuery vẫn còn là vị vua thống trị và khái niệm Single Page Application (SPA) như React hay Vue vẫn còn nằm ở đâu đó ngoài vũ trụ, thì chắc chắn anh em không thể không biết đến một "câu thần chú" huyền thoại: javascript:void(0);.

Ngày đó, đoạn code này xuất hiện dày đặc trong các file HTML, nằm chễm chệ bên trong thuộc tính href của các thẻ <a>. Nó từng là bí kíp bỏ túi của mọi lập trình viên web. Nhưng thời thế thay đổi, công nghệ tiến lên, và thứ từng là "cứu cánh" nay lại bị cộng đồng tẩy chay và coi là một Anti-pattern (mẫu code xấu).

Hôm nay, hãy cùng mình lật lại hồ sơ của thủ thuật này, xem bản chất nó là gì, tại sao ngày xưa người ta lại cuồng nó đến vậy, và lý do vì sao bây giờ chúng ta phải "tiệt nọc" nó khỏi dự án.

1. Bản chất của javascript:void(0); là gì?

Thực chất, đây là sự kết hợp của hai thứ: Pseudo-URL (URL giả) và toán tử void trong JavaScript.

  • javascript:: Khi bạn đặt cái này vào thuộc tính href, trình duyệt sẽ hiểu là: "À, đừng cố tải một trang web mới, thay vào đó hãy thực thi đoạn mã JavaScript nằm đằng sau dấu hai chấm".
  • Toán tử void: Trong JS, void là một toán tử cực kỳ thú vị. Nó sẽ nhận vào một biểu thức, tính toán biểu thức đó (nếu có), và luôn luôn trả về undefined.
  • 0: Chỉ là một đối số truyền vào cho ngắn gọn. Bạn viết void(0), void(1), hay void("Hello") thì kết quả cuối cùng mà trình duyệt nhận được vẫn là undefined.

Tóm lại: Khi người dùng click vào thẻ link có href="javascript:void(0);", trình duyệt sẽ chạy đoạn JS đó, nhận về kết quả undefined, và hệ quả là... nó quyết định không làm gì cả (không chuyển trang, không tải lại trang).

2. Tại sao các "tiền bối" lại thích dùng nó?

Vấn đề xuất phát từ bản chất của thẻ <a> (Anchor). Thẻ này sinh ra là để chứa các liên kết (Links). Khi bạn click vào, mặc định trình duyệt sẽ đưa bạn đi nơi khác.

Nhưng trong các ứng dụng web tương tác, có những lúc chúng ta muốn người dùng click vào một dòng chữ giống-hệt-một-cái-link (có gạch chân, đổi màu khi hover chuột) nhưng không phải để chuyển trang, mà là để kích hoạt một sự kiện JavaScript (như mở một cái Popup Modal, thả xuống một cái Menu dropdown, hay gửi một request AJAX).

Lúc đó, anh em dev có hai lựa chọn để ngăn trình duyệt chuyển trang:

  • Cách 1: Dùng href="#": Cách này siêu ngắn. Nhưng nó có một nhược điểm chí mạng: dấu # đại diện cho "đầu trang" (top of the page). Nếu người dùng đang cuộn chuột ở tít dưới đáy trang và click vào link này, trang web sẽ bị giật ngược lên trên cùng. Trải nghiệm cực kỳ tệ hại.
  • Cách 2: Dùng href="javascript:void(0);": Đây chính là "chân ái". Trang web sẽ đứng im phăng phắc, không giật, không nhảy, nhường lại toàn bộ sân khấu cho sự kiện onclick xử lý.

Đó là lý do mà void(0) thống trị các dự án web suốt một thập kỷ.

3. Tại sao bây giờ nó lại bị coi là "Tội đồ"?

Dù giải quyết được bài toán giật màn hình, nhưng ở thời điểm hiện tại, việc lạm dụng javascript:void(0); sẽ kéo theo một loạt hệ lụy:

  • Phá vỡ tính Ngữ nghĩa (Semantic HTML): Thẻ <a> sinh ra là để ĐIỀU HƯỚNG (Navigation). Nếu một thành phần trên giao diện không dẫn bạn đến một URL cụ thể, thì nó không xứng đáng là một thẻ <a>. Việc ép thẻ <a> làm nhiệm vụ kích hoạt chức năng (như một nút bấm) là một sự lừa dối về mặt cấu trúc.
  • Kẻ thù của Accessibility (Khả năng truy cập): Hãy tưởng tượng những người khiếm thị sử dụng phần mềm đọc màn hình (Screen Reader). Khi phần mềm quét qua, nó sẽ đọc là: "Link, Mở giỏ hàng". Người khiếm thị kỳ vọng họ sẽ được chuyển đến trang giỏ hàng. Nhưng khi bấm vào, chẳng có gì chuyển hướng cả. Điều này gây bối rối và làm giảm chất lượng sản phẩm của bạn.
  • Bị chặn bởi Content Security Policy (CSP): Các hệ thống bảo mật hiện đại ngày nay thường cấu hình CSP cực kỳ khắt khe để chống lại các cuộc tấn công XSS (Cross-Site Scripting). Rất nhiều bộ luật CSP mặc định cấm thực thi các inline-script (như javascript: trong HTML). Nếu dự án của bạn bật CSP, toàn bộ các thẻ chứa void(0) sẽ lăn ra chết cứng và báo lỗi đỏ lòm trong Console.

4. Giải pháp thay thế chuẩn mực: Trả lại tên cho em

Để dọn dẹp thứ "bùa ngải" này khỏi hệ thống, tư duy của chúng ta cần thay đổi: Nếu nó kích hoạt một hành động, nó phải là Button. Nếu nó đi đến một nơi khác, nó là Link.

Giải pháp 1: Sử dụng thẻ <button> Thay vì dùng thẻ <a>, hãy dùng <button type="button">. Thẻ button sinh ra chính xác là để bắt các sự kiện click thực thi logic JS. Nếu bạn không thích cái viền cục mịch của nút bấm, chỉ cần tốn 3 dòng CSS để tẩy trang cho nó trông giống hệt một đường link:

.btn-link {
    background: none;
    border: none;
    color: blue;
    text-decoration: underline;
    cursor: pointer;
}

Giải pháp 2: Sử dụng event.preventDefault() Nếu vì một lý do bất khả kháng nào đó (như dùng chung một component, hoặc do CSS framework ép buộc) mà bạn VẪN PHẢI dùng thẻ <a>, thì hãy để thuộc tính href chứa một đường dẫn dự phòng hợp lý, và xử lý việc chặn chuyển trang bên trong mã JavaScript:

<a href="/fallback-url" id="myLink">Click me</a>
// JavaScript
document.getElementById("myLink").addEventListener("click", function(event) {
    event.preventDefault(); // Phép thuật chặn chuyển trang nằm ở đây
    
    // Viết logic của bạn ở dưới
    console.log("Đã click, nhưng trang không bị chuyển đi đâu cả!");
});

Quá khứ đã qua, những gì thuộc về lịch sử hãy để nó ngủ yên. Đã đến lúc chúng ta code dứt khoát hơn, sử dụng các thẻ HTML đúng mục đích thiết kế của nó và nói lời tạm biệt với người bạn già javascript:void(0); để hướng tới những sản phẩm web chuẩn mực hơn. Happy coding!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí