Tìm hiểu chuyên sâu về sự kiện JavaScript

preventDefaultstopPropagation: thời điểm sử dụng phương thức nào và chức năng chính xác của mỗi phương thức.

Event.stopPropagation() và Event.preventDefault()

Việc xử lý sự kiện JavaScript thường đơn giản. Điều này đặc biệt đúng khi xử lý một cấu trúc HTML đơn giản (tương đối phẳng). Mọi thứ sẽ được liên quan nhiều hơn khi các sự kiện đang di chuyển (hoặc lan truyền) thông qua một hệ phân cấp các phần tử. Đây thường là khi nhà phát triển liên hệ với stopPropagation() và/hoặc preventDefault() để giải quyết các vấn đề họ đang gặp phải. Nếu bạn đã từng nghĩ: "Tôi sẽ thử preventDefault(). Nếu cách này không hiệu quả, tôi sẽ thử stopPropagation() và nếu cách đó không hiệu quả, tôi sẽ thử cả hai", thì bài viết này là dành cho bạn! Tôi sẽ giải thích chính xác chức năng của mỗi phương thức, thời điểm sử dụng phương pháp nào và cung cấp cho bạn nhiều ví dụ hiệu quả để bạn khám phá. Mục tiêu của tôi là giúp bạn xoá bỏ tình trạng nhầm lẫn.

Trước khi tìm hiểu sâu hơn, điều quan trọng là chúng ta cần điểm qua về hai cách xử lý sự kiện có thể dùng trong JavaScript (trong tất cả các trình duyệt hiện đại là Internet Explorer trước phiên bản 9 không hỗ trợ tính năng ghi lại sự kiện).

Kiểu tạo sự kiện (chụp ảnh và tạo bong bóng)

Tất cả các trình duyệt hiện đại đều hỗ trợ ghi lại sự kiện, nhưng các nhà phát triển rất hiếm khi sử dụng tính năng này. Điều thú vị là đó là hình thức sự kiện duy nhất mà Netscape hỗ trợ ban đầu. Đối thủ lớn nhất của Netscape là Microsoft Internet Explorer hoàn toàn không hỗ trợ tính năng ghi lại sự kiện mà chỉ hỗ trợ một kiểu sự kiện khác có tên là bong bóng sự kiện. Khi W3C được tạo, họ thấy có giá trị ở cả hai kiểu sự kiện và tuyên bố rằng các trình duyệt sẽ hỗ trợ cả hai, thông qua một thông số thứ ba cho phương thức addEventListener. Ban đầu, tham số đó chỉ là một giá trị boolean, nhưng tất cả các trình duyệt hiện đại đều hỗ trợ đối tượng options làm tham số thứ ba. Bạn có thể sử dụng đối tượng này để chỉ định (cùng với các tham số khác) nếu muốn sử dụng tính năng ghi lại sự kiện:

someElement.addEventListener('click', myClickHandler, { capture: true | false });

Lưu ý rằng đối tượng options cũng như thuộc tính capture là không bắt buộc. Nếu một trong hai bị bỏ qua, giá trị mặc định cho capture sẽ là false, nghĩa là tính năng bong bóng sự kiện sẽ được sử dụng.

Ghi hình sự kiện

Trình xử lý sự kiện "lắng nghe trong giai đoạn ghi lại" có nghĩa là gì? Để hiểu được điều này, chúng ta cần biết cách các sự kiện bắt nguồn và cách chúng di chuyển. Sau đây là đúng đối với tất cả sự kiện, ngay cả khi bạn (với tư cách là nhà phát triển) không tận dụng, quan tâm hay suy nghĩ về sự kiện đó.

Tất cả các sự kiện đều bắt đầu tại cửa sổ và trước tiên đều chuyển qua giai đoạn chụp ảnh. Điều này có nghĩa là khi một sự kiện được gửi đi, sự kiện đó sẽ khởi động cửa sổ và di chuyển "xuống" về phần tử mục tiêu đầu tiên. Điều này xảy ra ngay cả khi bạn chỉ nghe trong giai đoạn bong bóng. Hãy xem xét mã đánh dấu và JavaScript mẫu sau đây:

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('#C was clicked');
  },
  true,
);

Khi người dùng nhấp vào phần tử #C, một sự kiện bắt nguồn tại window sẽ được gửi đi. Sau đó, sự kiện này sẽ truyền qua các thành phần con cháu như sau:

window => document => <html> => <body> => và cứ thế cho đến khi đạt đến mục tiêu.

Sẽ không có vấn đề gì nếu không theo dõi sự kiện nhấp chuột ở phần tử window/document hoặc <html> hay <body> (hoặc bất kỳ phần tử nào khác trên đường đến mục tiêu của phần tử đó). Một sự kiện vẫn bắt nguồn tại window và bắt đầu hành trình như vừa mô tả.

Trong ví dụ của chúng ta, sự kiện nhấp chuột sau đó sẽ truyền tải (đây là một từ quan trọng vì nó sẽ liên kết trực tiếp với cách hoạt động của phương thức stopPropagation() và sẽ được giải thích sau trong tài liệu này) từ window đến phần tử mục tiêu (trong trường hợp này là #C) thông qua mọi phần tử giữa window#C.

Điều này có nghĩa là sự kiện nhấp chuột sẽ bắt đầu lúc window và trình duyệt sẽ hỏi các câu hỏi sau:

"Có thiết bị nào đang theo dõi sự kiện nhấp chuột trên window trong giai đoạn ghi lại không?" Nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt. Trong ví dụ của chúng tôi, không có gì xảy ra, do đó sẽ không có trình xử lý nào kích hoạt.

Tiếp theo, sự kiện này sẽ truyền tải đến document và trình duyệt sẽ hỏi: "Có bất kỳ mục nào đang nghe sự kiện nhấp chuột trên document trong giai đoạn ghi lại không?" Nếu có, trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện này sẽ truyền tải phần tử <html> và trình duyệt sẽ hỏi: "Có nội dung nào đang nghe lượt nhấp vào phần tử <html> trong giai đoạn ghi không?" Nếu có, trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện này sẽ truyền tải phần tử <body> và trình duyệt sẽ hỏi: "Có nội dung nào đang nghe sự kiện nhấp chuột trên phần tử <body> trong giai đoạn ghi không?" Nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện này sẽ truyền bá đến phần tử #A. Một lần nữa, trình duyệt sẽ hỏi: "Có bất kỳ nội dung nào đang nghe sự kiện nhấp chuột trên #A trong giai đoạn ghi lại không và nếu có, trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện sẽ truyền bá đến phần tử #B (và câu hỏi tương tự sẽ được đặt ra).

Cuối cùng, sự kiện này sẽ đạt đến mục tiêu và trình duyệt sẽ hỏi: "Có bất kỳ mục nào đang nghe sự kiện nhấp chuột trên phần tử #C trong giai đoạn chụp không?" Câu trả lời lần này là "có!" Khoảng thời gian ngắn này khi sự kiện tại mục tiêu, được gọi là "giai đoạn mục tiêu". Tại thời điểm này, trình xử lý sự kiện sẽ kích hoạt, trình duyệt sẽ console.log "#C đã được nhấp" và sau đó chúng ta hoàn tất, đúng không? Sai! Chúng tôi hoàn toàn không xong. Quá trình này vẫn tiếp tục, nhưng giờ đây sẽ chuyển sang giai đoạn sủi bọt.

Bong bóng sự kiện

Trình duyệt sẽ hỏi:

"Có thiết bị nào đang nghe sự kiện nhấp chuột trên #C trong giai đoạn nổi không?" Hãy đặc biệt chú ý tại đây. Bạn hoàn toàn có thể theo dõi các lượt nhấp (hoặc bất kỳ loại sự kiện nào) trong cả giai đoạn ghi lại giai đoạn bong bóng. Và nếu bạn đã kết nối trình xử lý sự kiện ở cả hai giai đoạn (ví dụ: gọi .addEventListener() 2 lần, một lần với capture = true và một lần với capture = false), thì có, cả hai trình xử lý sự kiện sẽ hoàn toàn kích hoạt cho cùng một phần tử. Nhưng một điều quan trọng cần lưu ý là các thiết bị này kích hoạt theo nhiều giai đoạn (một trong giai đoạn chụp và một trong giai đoạn nổi).

Tiếp theo, sự kiện sẽ truyền bá (thường được gọi là "bong bóng trò chuyện" vì có vẻ như sự kiện đang di chuyển "lên" cây DOM) đến phần tử mẹ #B và trình duyệt sẽ hỏi: "Có bất kỳ thứ gì đang nghe các sự kiện nhấp chuột trên #B trong giai đoạn bong bóng không?" Trong ví dụ của chúng tôi, không có trình xử lý nào kích hoạt.

Tiếp theo, sự kiện này sẽ tạo bong bóng trò chuyện sang #A và trình duyệt sẽ hỏi: "Có bất kỳ mục nào đang lắng nghe sự kiện nhấp chuột trên #A trong giai đoạn bong bóng không?"

Tiếp theo, sự kiện này sẽ hiện bong bóng trò chuyện <body>: "Có bất kỳ thứ gì đang nghe sự kiện nhấp chuột trên phần tử <body> trong giai đoạn nổi không?"

Tiếp theo là phần tử <html>: "Có bất kỳ mục nào đang nghe sự kiện nhấp chuột trên phần tử <html> trong giai đoạn bong bóng không?

Tiếp theo là document: "Có bất kỳ điều gì đang nghe sự kiện nhấp chuột trên document trong giai đoạn nổi không?"

Cuối cùng là window: "Có bất kỳ điều gì đang nghe sự kiện nhấp chuột trên cửa sổ trong giai đoạn bong bóng không?"

Chà! Đó là một hành trình dài và sự kiện của chúng tôi bây giờ có lẽ đã rất mệt mỏi, nhưng bạn có tin hay không, đó là hành trình mà mọi sự kiện đều phải trải qua! Trong hầu hết trường hợp, bạn không bao giờ nhận thấy điều này vì các nhà phát triển thường chỉ quan tâm đến một giai đoạn sự kiện hoặc giai đoạn khác (và đó thường là giai đoạn bong bóng).

Bạn nên dành thời gian để ghi lại sự kiện, tạo bong bóng và ghi nhật ký một số ghi chú vào bảng điều khiển khi trình xử lý kích hoạt. Sẽ rất giàu thông tin khi nhận thấy lộ trình của một sự kiện. Dưới đây là một ví dụ theo dõi mọi phần tử ở cả hai giai đoạn.

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>
document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in capturing phase');
  },
  true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in capturing phase');
  },
  true,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in capturing phase');
  },
  true,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in capturing phase');
  },
  true,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in capturing phase');
  },
  true,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in capturing phase');
  },
  true,
);

document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in bubbling phase');
  },
  false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in bubbling phase');
  },
  false,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in bubbling phase');
  },
  false,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in bubbling phase');
  },
  false,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in bubbling phase');
  },
  false,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in bubbling phase');
  },
  false,
);

Kết quả xuất ra trong bảng điều khiển sẽ phụ thuộc vào phần tử bạn nhấp vào. Nếu đã nhấp vào phần tử "sâu nhất" trong cây DOM (phần tử #C), bạn sẽ thấy từng phần tử trong số các trình xử lý sự kiện này kích hoạt. Với một chút định kiểu CSS để giúp bạn thấy rõ hơn phần tử nào, dưới đây là phần tử #C đầu ra của bảng điều khiển (cũng kèm theo ảnh chụp màn hình):

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"

Bạn có thể tương tác chơi trò chơi này trong phần minh hoạ trực tiếp bên dưới. Nhấp vào phần tử #C rồi quan sát kết quả trên bảng điều khiển.

event.stopPropagation()

Với hiểu biết về nguồn gốc và cách sự kiện di chuyển (tức là truyền) qua DOM trong cả giai đoạn ghi lại và bong bóng, giờ đây, chúng ta có thể chú ý đến event.stopPropagation().

Có thể gọi phương thức stopPropagation() trên (hầu hết) các sự kiện DOM gốc. Tôi nói "hầu hết" vì có một số đối tượng gọi phương thức này sẽ không làm gì (vì sự kiện không truyền để bắt đầu). Các sự kiện như focus, blur, load, scroll và một vài sự kiện khác thuộc danh mục này. Bạn có thể gọi stopPropagation() nhưng sẽ không có gì thú vị xảy ra vì những sự kiện này không lan truyền.

Nhưng stopPropagation có chức năng gì?

Nó có tác dụng, gần như giống như những gì nó nói. Khi bạn gọi phương thức này, sự kiện sẽ ngừng truyền đến bất kỳ phần tử nào mà nó sẽ truyền đến từ thời điểm đó. Điều này đúng cho cả hai hướng (ghi hình và bong bóng). Vì vậy, nếu bạn gọi stopPropagation() ở bất kỳ đâu trong giai đoạn chụp, sự kiện sẽ không bao giờ chuyển sang giai đoạn mục tiêu hoặc giai đoạn nổi bong bóng. Nếu bạn gọi hiệu ứng này trong giai đoạn bong bóng, thì hiệu ứng hình ảnh sẽ trải qua giai đoạn chụp nhưng sẽ không còn "bong bóng lên" từ thời điểm bạn gọi hiệu ứng.

Quay lại với mã đánh dấu trong ví dụ này, bạn nghĩ điều gì sẽ xảy ra nếu chúng ta gọi stopPropagation() trong giai đoạn thu thập ở phần tử #B?

Kết quả sẽ là:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"

Bạn có thể tương tác chơi trò chơi này trong phần minh hoạ trực tiếp bên dưới. Nhấp vào phần tử #C trong bản minh hoạ trực tiếp rồi quan sát kết quả trên bảng điều khiển.

Còn việc dừng truyền tải tại #A trong giai đoạn bong bóng thì sao? Điều đó sẽ dẫn đến kết quả sau:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"

Bạn có thể tương tác chơi trò chơi này trong phần minh hoạ trực tiếp bên dưới. Nhấp vào phần tử #C trong bản minh hoạ trực tiếp rồi quan sát kết quả trên bảng điều khiển.

Thêm một lần nữa, chỉ để giải trí. Điều gì sẽ xảy ra nếu chúng ta gọi stopPropagation() trong giai đoạn mục tiêu của #C? Hãy nhớ rằng "giai đoạn mục tiêu" là tên được đặt cho khoảng thời gian khi sự kiện mục tiêu. Kết quả sẽ là:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"

Xin lưu ý rằng trình xử lý sự kiện cho #C, trong đó chúng ta ghi nhật ký "nhấp vào #C trong giai đoạn ghi lại" vẫn thực thi, nhưng trình xử lý sự kiện mà chúng ta ghi nhật ký "nhấp vào #C trong giai đoạn bong bóng" thì không. Điều này hoàn toàn hợp lý. Chúng tôi gọi stopPropagation() từ phiên bản trước, vì vậy đó là thời điểm ngừng truyền sự kiện.

Bạn có thể tương tác chơi trò chơi này trong phần minh hoạ trực tiếp bên dưới. Nhấp vào phần tử #C trong bản minh hoạ trực tiếp rồi quan sát kết quả trên bảng điều khiển.

Bạn nên thử thực hiện bất kỳ bản minh hoạ trực tiếp nào nêu trên. Hãy thử chỉ nhấp vào phần tử #A hoặc chỉ nhấp vào phần tử body. Hãy thử dự đoán điều gì sẽ xảy ra và sau đó quan sát xem bạn có đúng hay không. Tại thời điểm này, bạn có thể dự đoán khá chính xác.

event.stopImmediatePropagation()

Phương pháp lạ và không được sử dụng này có gì? Phương thức này tương tự như stopPropagation, nhưng thay vì ngăn một sự kiện di chuyển sang phần tử con (giữ lại) hoặc đối tượng cấp trên (bốc bóng), phương thức này chỉ áp dụng khi bạn có nhiều trình xử lý sự kiện kết nối với một phần tử duy nhất. Vì addEventListener() hỗ trợ kiểu sự kiện đa hướng, nên bạn hoàn toàn có thể kết nối một trình xử lý sự kiện với một phần tử nhiều lần. Khi điều này xảy ra, (trong hầu hết các trình duyệt), trình xử lý sự kiện được thực thi theo thứ tự kết nối. Việc gọi stopImmediatePropagation() sẽ ngăn mọi trình xử lý tiếp theo kích hoạt. Hãy xem ví dụ sau đây:

<html>
  <body>
    <div id="A">I am the #A element</div>
  </body>
</html>
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I shall run first!');
  },
  false,
);

document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I shall run second!');
    e.stopImmediatePropagation();
  },
  false,
);

document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
  },
  false,
);

Ví dụ trên sẽ cho ra kết quả sau đây trên bảng điều khiển:

"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"

Xin lưu ý rằng trình xử lý sự kiện thứ ba không bao giờ chạy do trình xử lý sự kiện thứ hai gọi e.stopImmediatePropagation(). Nếu chúng tôi gọi e.stopPropagation(), trình xử lý thứ ba sẽ vẫn chạy.

event.preventDefault()

Nếu stopPropagation() ngăn một sự kiện di chuyển "xuống dưới" (chụp hình) hoặc "lên trên" (bong bóng), thì preventDefault() sẽ làm gì? Có vẻ như hoạt động này có hoạt động tương tự. Có phải không?

Thực ra là không. Mặc dù cả hai thường xuyên nhầm lẫn, nhưng thực ra họ không có liên quan gì đến nhau. Khi bạn thấy preventDefault(), hãy thêm từ "hành động" trong đầu bạn. Hãy nghĩ "ngăn chặn hành động mặc định".

Và thao tác mặc định mà bạn có thể yêu cầu là gì? Thật không may, câu trả lời không hoàn toàn rõ ràng vì nó phụ thuộc nhiều vào sự kết hợp yếu tố + sự kiện được đề cập. Và để làm cho vấn đề trở nên khó hiểu hơn nữa, đôi khi không có hành động mặc định nào cả!

Hãy bắt đầu bằng một ví dụ rất đơn giản để dễ hiểu. Bạn mong đợi điều gì xảy ra khi nhấp vào một đường liên kết trên trang web? Rõ ràng là bạn mong muốn trình duyệt sẽ điều hướng đến URL mà đường liên kết đó chỉ định. Trong trường hợp này, phần tử là thẻ liên kết và sự kiện là sự kiện nhấp chuột. Tổ hợp đó (<a> + click) có "thao tác mặc định" là điều hướng đến href của đường liên kết. Nếu bạn muốn ngăn trình duyệt thực hiện hành động mặc định đó thì sao? Nghĩa là, giả sử bạn muốn ngăn trình duyệt chuyển đến URL do thuộc tính href của phần tử <a> chỉ định? Đây là những gì preventDefault() sẽ làm cho bạn. Hãy xem xét ví dụ sau:

<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
  'click',
  function (e) {
    e.preventDefault();
    console.log('Maybe we should just play some of their music right here instead?');
  },
  false,
);

Bạn có thể tương tác chơi trò chơi này trong phần minh hoạ trực tiếp bên dưới. Nhấp vào đường liên kết The Avett Brothers và quan sát kết quả trên bảng điều khiển (và thực tế là bạn không được chuyển hướng đến trang web của Avett Brothers).

Thông thường, khi người dùng nhấp vào đường liên kết có nhãn là The Avett Brothers, thì người dùng sẽ phải duyệt xem www.theavettbrothers.com. Tuy nhiên, trong trường hợp này, chúng ta đã kết nối một trình xử lý sự kiện nhấp chuột với phần tử <a> và chỉ định rằng cần ngăn chặn hành động mặc định. Do đó, khi người dùng nhấp vào đường liên kết này, họ sẽ không được chuyển đến bất cứ đâu và thay vào đó, bảng điều khiển sẽ chỉ ghi nhật ký "Có lẽ chúng ta chỉ nên phát một vài bản nhạc của họ ngay tại đây?"

Những kiểu kết hợp thành phần/sự kiện nào khác cho phép bạn ngăn thao tác mặc định? Tôi không thể liệt kê hết tất cả và đôi khi bạn phải thử nghiệm để xem. Nhưng xin lưu ý một số điểm sau đây:

  • Phần tử <form> + sự kiện "submit": preventDefault() cho tổ hợp này sẽ ngăn việc gửi biểu mẫu. Cách này rất hữu ích nếu bạn muốn thực hiện việc xác thực và nếu xảy ra lỗi, bạn có thể gọi lệnh preventDefault có điều kiện để ngăn biểu mẫu gửi đi.

  • Phần tử <a> + sự kiện "nhấp": preventDefault() cho tổ hợp này ngăn trình duyệt chuyển đến URL được chỉ định trong thuộc tính href của phần tử <a>.

  • document + sự kiện "con lăn chuột": preventDefault() cho kiểu kết hợp này ngăn việc cuộn trang bằng con lăn chuột (nhưng thao tác cuộn bằng bàn phím vẫn sẽ hoạt động).
    ↜ Việc này yêu cầu gọi addEventListener() bằng { passive: false }.

  • document + sự kiện "keydown": preventDefault() cho kiểu kết hợp này có thể gây chết người. Tính năng này kết xuất trang mà phần lớn là vô dụng, ngăn việc cuộn bàn phím, thao tác bằng thẻ và làm nổi bật bàn phím.

  • document + sự kiện "chuột xuống": preventDefault() cho tổ hợp này sẽ ngăn việc làm nổi bật văn bản bằng chuột và mọi hành động "mặc định" khác mà một người dùng sẽ gọi khi di chuột xuống.

  • Phần tử <input> + sự kiện "nhấn phím": preventDefault() cho tổ hợp này sẽ ngăn các ký tự do người dùng nhập truy cập vào phần tử đầu vào (nhưng bạn đừng nên làm như vậy vì hiếm khi có lý do hợp lệ, nếu có).

  • document + sự kiện "contextmenu": preventDefault() cho tổ hợp này ngăn trình đơn theo bối cảnh của trình duyệt gốc xuất hiện khi người dùng nhấp chuột phải hoặc nhấn và giữ (hoặc bất kỳ cách nào khác mà trình đơn theo bối cảnh có thể xuất hiện).

Đây chưa phải danh sách đầy đủ, nhưng hy vọng nó sẽ giúp bạn hiểu rõ cách sử dụng preventDefault().

Một câu đùa thực tế vui nhộn?

Điều gì sẽ xảy ra nếu bạn stopPropagation() preventDefault() trong giai đoạn chụp, bắt đầu từ tài liệu? Có sự hài hước! Đoạn mã sau đây sẽ hiển thị bất kỳ trang web nào hoàn toàn vô dụng:

function preventEverything(e) {
  e.preventDefault();
  e.stopPropagation();
  e.stopImmediatePropagation();
}

document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });

Tôi thực sự không biết tại sao các em lại muốn làm điều này (ngoại trừ việc đùa giỡn ai đó), nhưng sẽ rất hữu ích nếu nghĩ về những gì đang xảy ra ở đây và hiểu được lý do nó tạo ra tình huống đó.

Tất cả các sự kiện đều bắt nguồn tại window, vì vậy, trong đoạn mã này, chúng ta sẽ dừng theo dõi, tất cả các sự kiện click, keydown, mousedown, contextmenumousewheel từ khi tiếp cận bất kỳ phần tử nào có thể đang theo dõi các sự kiện đó. Chúng tôi cũng gọi stopImmediatePropagation để mọi trình xử lý kết nối với tài liệu sau trình xử lý này cũng sẽ bị cản trở.

Lưu ý rằng stopPropagation()stopImmediatePropagation() không phải (ít nhất là không phải là chủ yếu) là những gì khiến trang trở nên vô ích. Chúng chỉ đơn giản là ngăn sự kiện chuyển đến mục đích của nó.

Tuy nhiên, chúng ta cũng gọi preventDefault(). Bạn sẽ nhớ thao tác này sẽ ngăn hành động mặc định. Vì vậy, mọi thao tác mặc định (như cuộn con lăn chuột, cuộn bằng bàn phím hoặc đánh dấu, thao tác bằng thẻ, nhấp vào đường liên kết, hiển thị trình đơn theo bối cảnh, v.v.) đều bị ngăn chặn, khiến trang có trạng thái khá vô dụng.

Bản minh hoạ trực tiếp

Để khám phá lại tất cả các ví dụ trong bài viết này ở một nơi, hãy xem bản minh hoạ được nhúng bên dưới.

Xác nhận

Hình ảnh chính của Tom Wilson trên Unsplash.