Javascript 瀏覽器默認(rèn)行為

2023-02-17 10:54 更新

許多事件會(huì)自動(dòng)觸發(fā)瀏覽器執(zhí)行某些行為。

例如:

  • 點(diǎn)擊一個(gè)鏈接 —— 觸發(fā)導(dǎo)航(navigation)到該 URL。點(diǎn)擊表單的提交按鈕 —— 觸發(fā)提交到服務(wù)器的行為。
  • 在文本上按下鼠標(biāo)按鈕并移動(dòng) —— 選中文本。

如果我們使用 JavaScript 處理一個(gè)事件,那么我們通常不希望發(fā)生相應(yīng)的瀏覽器行為。而是想要實(shí)現(xiàn)其他行為進(jìn)行替代。

阻止瀏覽器行為

有兩種方式來告訴瀏覽器我們不希望它執(zhí)行默認(rèn)行為:

  • 主流的方式是使用 ?event? 對象。有一個(gè) ?event.preventDefault()? 方法。
  • 如果處理程序是使用 ?on<event>?(而不是 ?addEventListener?)分配的,那返回 ?false? 也同樣有效。

在下面這個(gè)示例中,點(diǎn)擊鏈接不會(huì)觸發(fā)導(dǎo)航(navigation),瀏覽器不會(huì)執(zhí)行任何操作:

<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>

在下一個(gè)示例中,我們將使用此技術(shù)來創(chuàng)建 JavaScript 驅(qū)動(dòng)的菜單。

從處理程序返回 ?false? 是一個(gè)例外

事件處理程序返回的值通常會(huì)被忽略。

唯一的例外是從使用 on<event> 分配的處理程序中返回的 return false。

在所有其他情況下,return 值都會(huì)被忽略。并且,返回 true 沒有意義。

示例:菜單

考慮一個(gè)網(wǎng)站菜單,如下所示:

<ul id="menu" class="menu">
  <li><a href="/html">HTML</a></li>
  <li><a href="/javascript">JavaScript</a></li>
  <li><a href="/css">CSS</a></li>
</ul>

下面經(jīng)過 CSS 渲染的外觀:


菜單項(xiàng)是通過使用 HTML 鏈接 <a> 實(shí)現(xiàn)的,而不是使用按鈕 <button>。這樣做有幾個(gè)原因,例如:

  • 許多人喜歡使用“右鍵單擊” —— “在一個(gè)新窗口打開鏈接”。如果我們使用 ?<button>? 或 ?<span>?,這個(gè)效果就無法實(shí)現(xiàn)。
  • 搜索引擎在建立索引時(shí)遵循 ?<a href="...">? 鏈接。

所以我們在標(biāo)記(markup)中使用了 <a>。但通常我們打算處理 JavaScript 中的點(diǎn)擊。因此,我們應(yīng)該阻止瀏覽器默認(rèn)行為。

像這樣:

menu.onclick = function(event) {
  if (event.target.nodeName != 'A') return;

  let href = event.target.getAttribute('href');
  alert( href ); // ...可以從服務(wù)器加載,UI 生成等

  return false; // 阻止瀏覽器行為(不前往訪問 URL)
};

如果我們省略 return false,那么在我們的代碼執(zhí)行完畢后,瀏覽器將執(zhí)行它的“默認(rèn)行為” —— 導(dǎo)航至在 href 中的 URL。

順便說一句,這里使用事件委托會(huì)使我們的菜單更靈活。我們可以添加嵌套列表并使用 CSS 對其進(jìn)行樣式設(shè)置來實(shí)現(xiàn) “slide down” 的效果。

后續(xù)事件

某些事件會(huì)相互轉(zhuǎn)化。如果我們阻止了第一個(gè)事件,那就沒有第二個(gè)事件了。

例如,在 <input> 字段上的 mousedown 會(huì)導(dǎo)致在其中獲得焦點(diǎn),以及 focus 事件。如果我們阻止 mousedown 事件,在這就沒有焦點(diǎn)了。

這是因?yàn)闉g覽器行為在 mousedown 上被取消。如果我們用另一種方式進(jìn)行輸入,則仍然可以進(jìn)行聚焦。例如,可以使用 ?Tab? 鍵從第一個(gè)輸入切換到第二個(gè)輸入。但鼠標(biāo)點(diǎn)擊則不行。

處理程序選項(xiàng) “passive”

addEventListener 的可選項(xiàng) passive: true 向?yàn)g覽器發(fā)出信號(hào),表明處理程序?qū)⒉粫?huì)調(diào)用 preventDefault()

為什么需要這樣做?

移動(dòng)設(shè)備上會(huì)發(fā)生一些事件,例如 touchmove(當(dāng)用戶在屏幕上移動(dòng)手指時(shí)),默認(rèn)情況下會(huì)導(dǎo)致滾動(dòng),但是可以使用處理程序的 preventDefault() 來阻止?jié)L動(dòng)。

因此,當(dāng)瀏覽器檢測到此類事件時(shí),它必須首先處理所有處理程序,然后如果沒有任何地方調(diào)用 preventDefault,則頁面可以繼續(xù)滾動(dòng)。但這可能會(huì)導(dǎo)致 UI 中不必要的延遲和“抖動(dòng)”。

passive: true 選項(xiàng)告訴瀏覽器,處理程序不會(huì)取消滾動(dòng)。然后瀏覽器立即滾動(dòng)頁面以提供最大程度的流暢體驗(yàn),并通過某種方式處理事件。

對于某些瀏覽器(Firefox,Chrome),默認(rèn)情況下,touchstart 和 touchmove 事件的 passive 為 true

event.defaultPrevented

如果默認(rèn)行為被阻止,那么 event.defaultPrevented 屬性為 true,否則為 false。

這兒有一個(gè)有趣的用例。

你還記得我們在 冒泡和捕獲 一章中討論過的 event.stopPropagation(),以及為什么停止冒泡是不好的嗎?

有時(shí)我們可以使用 event.defaultPrevented 來代替,來通知其他事件處理程序,該事件已經(jīng)被處理。

我們來看一個(gè)實(shí)際的例子。

默認(rèn)情況下,瀏覽器在 contextmenu 事件(單擊鼠標(biāo)右鍵)時(shí),顯示帶有標(biāo)準(zhǔn)選項(xiàng)的上下文菜單。我們可以阻止它并顯示我們自定義的菜單,就像這樣:

<button>Right-click shows browser context menu</button>

<button oncontextmenu="alert('Draw our menu'); return false">
  Right-click shows our context menu
</button>

現(xiàn)在,除了該上下文菜單外,我們還想實(shí)現(xiàn)文檔范圍的上下文菜單。

右鍵單擊時(shí),應(yīng)該顯示最近的上下文菜單:

<p>Right-click here for the document context menu</p>
<button id="elem">Right-click here for the button context menu</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Document context menu");
  };
</script>

問題是,當(dāng)我們點(diǎn)擊 elem 時(shí),我們會(huì)得到兩個(gè)菜單:按鈕級(jí)和文檔級(jí)(事件冒泡)的菜單。

如何修復(fù)呢?其中一個(gè)解決方案是:“當(dāng)我們在按鈕處理程序中處理鼠標(biāo)右鍵單擊事件時(shí),我們阻止其冒泡”,使用 event.stopPropagation()

<p>Right-click for the document menu</p>
<button id="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    event.stopPropagation();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Document context menu");
  };
</script>

現(xiàn)在按鈕級(jí)菜單如期工作。但是代價(jià)太大,我們拒絕了任何外部代碼對右鍵點(diǎn)擊信息的訪問,包括收集統(tǒng)計(jì)信息的計(jì)數(shù)器等。這是非常不明智的。

另一個(gè)替代方案是,檢查 document 處理程序是否阻止了瀏覽器的默認(rèn)行為?如果阻止了,那么該事件已經(jīng)得到了處理,我們無需再對此事件做出反應(yīng)。

<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    if (event.defaultPrevented) return;

    event.preventDefault();
    alert("Document context menu");
  };
</script>

現(xiàn)在一切都可以正常工作了。如果我們有嵌套的元素,并且每個(gè)元素都有自己的上下文菜單,那么這也是可以運(yùn)行的。只需確保檢查每個(gè) contextmenu 處理程序中的 event.defaultPrevented。

event.stopPropagation() 和 event.preventDefault()

正如我們所看到的,event.stopPropagation() 和 event.preventDefault()(也被認(rèn)為是 return false)是兩個(gè)不同的東西。它們之間毫無關(guān)聯(lián)。

嵌套的上下文菜單結(jié)構(gòu)

還有其他實(shí)現(xiàn)嵌套上下文菜單的方式。其中之一是擁有一個(gè)具有 document.oncontextmenu 處理程序的全局對象,以及使我們能夠在其中存儲(chǔ)其他處理程序的方法。

該對象將捕獲任何右鍵單擊,瀏覽存儲(chǔ)的處理程序并運(yùn)行適當(dāng)?shù)奶幚沓绦颉?

但是,每段需要上下文菜單的代碼都應(yīng)該了解該對象,并使用它的幫助,而不是使用自己的 contextmenu 處理程序。

總結(jié)

有很多默認(rèn)的瀏覽器行為:

  • ?mousedown? —— 開始選擇(移動(dòng)鼠標(biāo)進(jìn)行選擇)。
  • 在 ?<input type="checkbox">? 上的 ?click? —— 選中/取消選中的 ?input?。
  • ?submit? —— 點(diǎn)擊 ?<input type="submit">? 或者在表單字段中按下 ?Enter? 鍵會(huì)觸發(fā)該事件,之后瀏覽器將提交表單。
  • ?keydown? —— 按下一個(gè)按鍵會(huì)導(dǎo)致將字符添加到字段,或者觸發(fā)其他行為。
  • ?contextmenu? —— 事件發(fā)生在鼠標(biāo)右鍵單擊時(shí),觸發(fā)的行為是顯示瀏覽器上下文菜單。
  • ……還有更多……

如果我們只想通過 JavaScript 來處理事件,那么所有默認(rèn)行為都是可以被阻止的。

想要阻止默認(rèn)行為 —— 可以使用 event.preventDefault() 或 return false。第二個(gè)方法只適用于通過 on<event> 分配的處理程序。

addEventListener 的 passive: true 選項(xiàng)告訴瀏覽器該行為不會(huì)被阻止。這對于某些移動(dòng)端的事件(像 touchstart 和 touchmove)很有用,用以告訴瀏覽器在滾動(dòng)之前不應(yīng)等待所有處理程序完成。

如果默認(rèn)行為被阻止,event.defaultPrevented 的值會(huì)變成 true,否則為 false。

保持語義,不要濫用

從技術(shù)上來說,通過阻止默認(rèn)行為并添加 JavaScript,我們可以自定義任何元素的行為。例如,我們可以使鏈接 <a> 像按鈕一樣工作,而按鈕 <button> 也可以像鏈接那樣工作(重定向到另一個(gè) URL 等)。

但我們通常應(yīng)該保留 HTML 元素的語義。例如 <a> 應(yīng)該表現(xiàn)為導(dǎo)航(navigation),而不是按鈕。

除了“只是一件好事”之外,這還會(huì)使你的 HTML 具有更好的可訪問性。

另外,如果我們考慮使用帶有 <a> 的示例,那么請注意:瀏覽器允許我們在新窗口中打開此類鏈接(通過右鍵單擊它們以及其他方式)。大家都喜歡這么做。但是,如果我們使用 JavaScript 讓按鈕行為表現(xiàn)得像鏈接,甚至使用 CSS 將其樣式設(shè)置成看起來也像鏈接,即使這樣,但仍然無法在按鈕上使用特定于 <a> 的瀏覽器功能。

任務(wù)


為什么 "return false" 不起作用?

重要程度: 3

為什么下面這段代碼中的 return false 不起作用?

<script>
  function handler() {
    alert( "..." );
    return false;
  }
</script>

<a  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  onclick="handler()">the browser will go to w3.org</a>

瀏覽器在點(diǎn)擊時(shí)會(huì)根據(jù) URL 進(jìn)行跳轉(zhuǎn),但這不是我們想要的。

如何修復(fù)它?


解決方案

當(dāng)瀏覽器讀取諸如 onclick 之類的 on* 特性(attribute)時(shí),瀏覽器會(huì)根據(jù)其內(nèi)容創(chuàng)建對應(yīng)的處理程序。

對于 onclick="handler()" 來說,函數(shù)是:

function(event) {
  handler() // onclick 的內(nèi)容
}

現(xiàn)在我們可以看到 handler() 的返回值并沒有被使用,也沒有對結(jié)果產(chǎn)生影響。

修復(fù)起來很簡單:

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  onclick="return handler()">w3.org</a>

我們也可以使用 event.preventDefault(),像這樣:

<script>
  function handler(event) {
    alert("...");
    event.preventDefault();
  }
</script>

<a  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  onclick="handler(event)">w3.org</a>

捕獲元素中的鏈接

重要程度: 5

使所有包含 ?id="contents"? 的元素內(nèi)的鏈接詢問用戶是否真的要離開。如果用戶不想離開,那就不離開。

細(xì)節(jié):

  • 元素內(nèi)的 HTML 可以被隨時(shí)動(dòng)態(tài)加載或重新生成,因此,我們無法找到所有鏈接并為其添加處理程序。這里使用事件委托。
  • 內(nèi)容中可能有嵌套的標(biāo)簽。鏈接中也是,例如 ?<a href=".."><i>...</i></a>?。

打開一個(gè)任務(wù)沙箱。


解決方案

這是一個(gè)很好的使用事件委托模式的案例。

在現(xiàn)實(shí)生活中,我們可以向服務(wù)器發(fā)送一個(gè) “l(fā)ogging” 請求而不是詢問,該請求會(huì)保存關(guān)于訪問者離開位置的信息?;蛘撸覀兛梢约虞d內(nèi)容,并將其顯示在頁面中(如果允許的話)。

我們只需要捕獲 contents.onclick,然后使用 confirm 來詢問用戶。一個(gè)好主意是使用 link.getAttribute('href') 來代替 link.href。詳情請參見解決方案。

使用沙箱打開解決方案。


圖冊

重要程度: 5

創(chuàng)建一個(gè)圖冊,通過點(diǎn)擊縮略圖可以更改主圖片。

P.S. 使用事件委托。

打開一個(gè)任務(wù)沙箱。


解決方案

解決方案是將處理程序分配給容器,并追蹤點(diǎn)擊。如果點(diǎn)擊在 <a> 鏈接上,則將 #largeImg 的 src 修改為該縮略圖的 href。

使用沙箱打開解決方案。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)