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

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

Việc xử lý sự kiện JavaScript thường rất đơn giản. Điều này đặc biệt đúng khi xử lý cấu trúc HTML đơn giản (tương đối phẳng). Mọi thứ có liên quan hơn một chút, mặc dù khi các sự kiện được di chuyển (hoặc phổ biến) thông qua một hệ phân cấp các phần tử. Đây thường là lúc các nhà phát triển tìm đến stopPropagation() và/hoặc preventDefault() để giải quyết các vấn đề mà họ đang gặp phải. Nếu bạn từng tự nhủ "Tôi sẽ thử preventDefault() và nếu không được thì tôi sẽ thử stopPropagation(), nếu không được thì 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 từng phương thức, thời điểm sử dụng phương thức nào và cung cấp cho bạn nhiều ví dụ hoạt động để bạn khám phá. Mục tiêu của tôi là giúp bạn giải tỏa mọi thắc mắc.

Tuy nhiên, trước khi đi sâu vào vấn đề, điều quan trọng là bạn phải nắm được sơ lược về hai loại cách xử lý sự kiện có thể thực hiện trong JavaScript (trong tất cả các trình duyệt hiện đại – Internet Explorer trước phiên bản 9 hoàn toàn không hỗ trợ tính năng ghi lại sự kiện).

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

Tất cả trình duyệt hiện đại đều hỗ trợ tính năng ghi lại sự kiện, nhưng nhà phát triển rất hiếm khi sử dụng tính năng này. Điều thú vị là đây là hình thức duy nhất của sự kiện mà Netscape ban đầu hỗ trợ. Đối thủ lớn nhất của Netscape, 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 gọi là sự kiện nổi. Khi W3C được thành lập, họ nhận thấy cả hai kiểu sự kiện đều có ưu điểm và tuyên bố rằng trình duyệt phải hỗ trợ cả hai, thông qua tham số thứ ba cho phương thức addEventListener. Ban đầu, tham số đó chỉ là một boolean, nhưng tất 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 tham số này để chỉ định (trong số các tham số khác) xem bạn có muốn sử dụng tính năng ghi lại sự kiện hay không:

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

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

Ghi lại sự kiện

Điều gì xảy ra nếu trình xử lý sự kiện của bạn đang "nghe trong giai đoạn chụp?" Để hiểu đ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. Điều sau đây đúng 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 hoặc suy nghĩ về sự kiện đó.

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

<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ừ window sẽ được gửi đi. Sau đó, sự kiện này sẽ truyền qua các phần tử con như sau:

window => document => <html> => <body> => v.v., cho đến khi đạt được mục tiêu.

Không quan trọng nếu không có phần tử nào đang nghe sự kiện nhấp chuột tại phần tử window hoặc document hoặc <html> hoặc <body> (hoặc bất kỳ phần tử nào khác trên đường đến mục tiêu của sự kiện). Sự kiện vẫn bắt nguồn từ 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 sẽ truyền (đâ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 ở phần sau của 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 sẽ bắt đầu tại window và trình duyệt sẽ đặt các câu hỏi sau:

"Có gì đang theo dõi sự kiện nhấp trên window trong giai đoạn chụp 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 ta, không có gì xảy ra nên không có trình xử lý nào sẽ kích hoạt.

Tiếp theo, sự kiện sẽ truyền đến document và trình duyệt sẽ hỏi: "Có gì đang nghe sự kiện nhấp trên document trong giai đoạn chụp 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 sẽ truyền đến phần tử <html> và trình duyệt sẽ hỏi: "Có gì đang theo dõi lượt nhấp vào phần tử <html> trong giai đoạn chụp 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 sẽ truyền đến phần tử <body> và trình duyệt sẽ hỏi: "Có gì đang theo dõi sự kiện nhấp trên phần tử <body> trong giai đoạn chụp 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 sẽ truyền tải đến phần tử #A. Một lần nữa, trình duyệt sẽ hỏi: "Có bất cứ nội dung nào đang nghe sự kiện nhấp chuột trên #A trong giai đoạn chụp 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 sẽ truyền tải đến phần tử #B (và cùng một câu hỏi).

Cuối cùng, sự kiện sẽ đạt được mục tiêu và trình duyệt sẽ hỏi: "Có gì đang nghe sự kiện nhấp 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 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 chưa hoàn tất. Quá trình này tiếp tục, nhưng giờ đây sẽ chuyển sang giai đoạn tạo bọt.

Sự kiện nổi

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

"Có gì đang theo dõi sự kiện nhấp trên #C trong giai đoạn nổi lên không?" Hãy chú ý kỹ ở đâ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 chụp giai đoạn nổi. Và nếu bạn đã kết nối trình xử lý sự kiện ở cả hai giai đoạn (ví dụ: bằng cách gọi .addEventListener() hai 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ẽ kích hoạt hoàn toàn cho cùng một phần tử. Tuy nhiên, cũng cần lưu ý rằng các sự kiện này kích hoạt ở các giai đoạn khác nhau (một sự kiện ở giai đoạn chụp và một sự kiện ở giai đoạn tạo bong bóng).

Tiếp theo, sự kiện này sẽ truyền tải (thường được gọi là "bong bóng" 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ó ai đang nghe 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 ta, không có gì xảy ra, vì vậy, không có trình xử lý nào sẽ kích hoạt.

Tiếp theo, sự kiện sẽ chuyển đến #A và trình duyệt sẽ hỏi: "Có gì đang nghe sự kiện nhấp trên #A trong giai đoạn chuyển lên trên không?"

Tiếp theo, sự kiện sẽ chuyển lên <body>: "Có gì đang theo dõi sự kiện nhấp trên phần tử <body> trong giai đoạn chuyển lên trên không?"

Tiếp theo, phần tử <html>: "Có gì đang theo dõi các sự kiện nhấp trên phần tử <html> trong giai đoạn tạo bong bóng không?

Tiếp theo, document: "Có gì đang theo dõi các sự kiện nhấp chuột trên document trong giai đoạn sủi bọt không?"

Cuối cùng, window: "Có gì đang nghe sự kiện nhấp trên cửa sổ trong giai đoạn nổi lên không?"

Chà! Đó là một hành trình dài và sự kiện của chúng ta có lẽ đã rất mệt mỏi. Tuy nhiên, dù bạn có tin hay không, đó chính là hành trình mà mọi sự kiện đều phải trải qua! Hầu hết thời gian, điều này không bao giờ được chú ý vì 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 sự kiện khác (và thường là giai đoạn tạo bọt).

Bạn nên dành thời gian tìm hiểu tính năng ghi lại sự kiện và sự kiện nổi cũng như ghi lại một số ghi chú vào bảng điều khiển khi trình xử lý kích hoạt. Bạn sẽ thấy rất rõ ràng đường dẫn mà một sự kiện đi qua. Dưới đây là ví dụ về việc theo dõi mọi phần tử trong 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ả của bảng điều khiển sẽ phụ thuộc vào phần tử mà 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 mọi trình xử lý sự kiện này đều kích hoạt. Định kiểu CSS một chút để giúp bạn biết rõ đó là phần tử nào, sau đây là phần tử đầu ra #C của bảng điều khiển (có cả ả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 với tính năng này trong bản minh hoạ trực tiếp bên dưới. Nhấp vào phần tử #C và quan sát kết quả trên bảng điều khiển.

event.stopPropagation()

Khi đã hiểu rõ nguồn gốc của sự kiện và cách sự kiện di chuyển (tức là truyền tải) thông qua DOM trong cả giai đoạn ghi lại và giai đoạn nổi bọt, giờ đây, chúng ta có thể chuyển sự chú ý sang event.stopPropagation().

Bạn 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ố trường hợp mà việc gọi phương thức này sẽ không làm gì cả (vì sự kiện không được truyền đến từ đầu). Các sự kiện như focus, blur, load, scroll và một số 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ị vì những sự kiện này không lan truyền.

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

Gần như là giống như nội dung của lời nói. Khi bạn gọi sự kiện đó, từ thời điểm đó, sự kiện sẽ ngừng truyền đến bất kỳ phần tử nào mà sự kiện đó sẽ truyền đến. Điều này đúng với cả hai hướng (ghi lại và chuyển lên trên). Vì vậy, nếu bạn gọi stopPropagation() ở bất kỳ đâu trong giai đoạn chụp, thì 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. Nếu bạn gọi phương thức này trong giai đoạn tạo bọt, thì phương thức này sẽ đã trải qua giai đoạn chụp, nhưng sẽ ngừng "bọt lên" từ điểm bạn gọi phương thức này.

Quay lại cùng một mã đánh dấu ví dụ, bạn nghĩ điều gì sẽ xảy ra nếu chúng ta gọi stopPropagation() trong giai đoạn chụp tại 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 với tính năng này trong bả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 và quan sát kết quả của bảng điều khiển.

Còn việc dừng quá trình truyền tại #A trong giai đoạn nổi bọt 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 với tính năng này trong bả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.

Một ví dụ nữa, chỉ để cho vui. Điều gì sẽ xảy ra nếu chúng ta gọi stopPropagation() trong giai đoạn mục tiêu cho #C? Hãy nhớ rằng "giai đoạn mục tiêu" là tên được đặt cho khoảng thời gian sự kiện tại mục tiêu. Kết quả sẽ như 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"

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 chụp" vẫn thực thi, nhưng trình xử lý mà chúng ta ghi nhật ký "nhấp vào #C trong giai đoạn tạo 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ừ sự kiện trước đây, do đó, quá trình truyền của sự kiện sẽ dừng lại.

Bạn có thể tương tác chơi trò chơi này trong bả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.

Trong bất kỳ bản minh hoạ trực tiếp nào trong số này, bạn nên thử nghiệm. 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 rồi quan sát xem bạn có chính xác 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 thức lạ và không thường dùng này là gì? Phương thức này tương tự như stopPropagation, nhưng thay vì ngăn sự kiện chuyển đến các phần tử con (ghi lại) hoặc phần tử mẹ (bùng nổ), phương thức này chỉ áp dụng khi bạn có nhiều trình xử lý sự kiện được kết nối với một phần tử. Vì addEventListener() hỗ trợ kiểu phát nhiều địa chỉ của sự kiện, nên bạn hoàn toàn có thể kết nối 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 sẽ được thực thi theo thứ tự được 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!"

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 ta gọi e.stopPropagation(), trình xử lý thứ ba vẫn sẽ chạy.

event.preventDefault()

Nếu stopPropagation() ngăn một sự kiện di chuyển "xuống" (ghi lại) hoặc "lên" (bùng nổ), thì preventDefault() sẽ làm gì? Có vẻ như nó cũng làm được những việc tương tự. Có phải không?

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

Hành động mặc định mà bạn có thể yêu cầu là gì? Rất tiếc, câu trả lời cho câu hỏi đó không rõ ràng lắm vì nó phụ thuộc nhiều vào tổ hợp thành phần + sự kiện có liên quan. Và để làm cho vấn đề trở nên khó hiểu hơn, đôi khi không có hành động mặc định nào cả!

Hãy bắt đầu với một ví dụ rất đơn giản để hiểu rõ. Điều gì sẽ xảy ra khi bạn nhấp vào một đường liên kết trên trang web? Rõ ràng là bạn muốn trình duyệt chuyển đến URL do đường liên kết đó chỉ định. Trong trường hợp này, phần tử là thẻ ký tự 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? Tức 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 với tính năng này trong bản minh hoạ trực tiếp bên dưới. Nhấp vào đường liên kết The Avett Brothers (Anh em nhà Avett) rồi 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 nhấp vào đường liên kết có nhãn The Avett Brothers, bạn sẽ được chuyển đến www.theavettbrothers.com. Tuy nhiên, trong trường hợp này, chúng ta đã kết nối trình xử lý sự kiện nhấp vào phần tử <a> và chỉ định rằng hành động mặc định sẽ bị ngăn chặn. Do đó, khi người dùng nhấp vào đường liên kết này, họ sẽ không được điều hướng đến bất kỳ đâu. Thay vào đó, bảng điều khiển sẽ chỉ ghi lại "Có lẽ chúng ta nên phát một số bản nhạc của họ ngay tại đây?"

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

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

  • Phần tử <a> + sự kiện "nhấp chuột": 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 "bánh xe chuột": preventDefault() cho tổ hợp này ngăn việc cuộn trang bằng bánh xe chuột (mặc dù việc cuộn bằng bàn phím vẫn hoạt động).
    ↜ Điều này yêu cầu gọi addEventListener() bằng { passive: false }.

  • document + sự kiện "keydown": preventDefault() cho tổ hợp này là gây tử vong. Điều này khiến trang trở nên vô dụng, ngăn việc cuộn, nhấn phím tab và làm nổi bật bằng bàn phím.

  • document + sự kiện "mousedown": 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 thao tác "mặc định" khác mà người dùng sẽ gọi bằng cách nhấn 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 không truy cập được vào phần tử nhập (nhưng đừng làm như vậy; hiếm khi có lý do hợp lệ cho việc này).

  • 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 không phải là danh sách đầy đủ, nhưng hy vọng nó sẽ giúp bạn nắm được cách sử dụng preventDefault().

Một trò đùa tinh nghịch thú vị?

Đ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? Sau đó là những tình huống 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 bạn sẽ muốn làm điều này (ngoại trừ có thể là đùa giỡn với ai đó), nhưng sẽ rất hữu ích nếu bạn nghĩ về những gì đang xảy ra ở đây và nhận ra lý do tại sao nó tạo ra tình huống đó.

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

Xin lưu ý rằng stopPropagation()stopImmediatePropagation() không phải là (ít nhất là không phải hầu hết) những yếu tố khiến trang trở nên vô dụng. Các trình chặn chỉ đơn giản là ngăn các sự kiện đến nơi mà chúng sẽ đến.

Tuy nhiên, chúng ta cũng gọi preventDefault(), như bạn đã nhớ, ngăn chặn thao tác mặc định. Vì vậy, mọi thao tác mặc định (như cuộn bằng con lăn chuột, cuộn bằng bàn phím hoặc làm nổi bật hoặc nhấn phím tab, 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, do đó khiến trang ở 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 ở cùng 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.