JavaScript 其他常見事件

2023-03-20 15:46 更新

資源事件 

beforeunload 事件 

beforeunload事件在窗口、文檔、各種資源將要卸載前觸發(fā)。它可以用來防止用戶不小心卸載資源。

如果該事件對(duì)象的returnValue屬性是一個(gè)非空字符串,那么瀏覽器就會(huì)彈出一個(gè)對(duì)話框,詢問用戶是否要卸載該資源。但是,用戶指定的字符串可能無法顯示,瀏覽器會(huì)展示預(yù)定義的字符串。如果用戶點(diǎn)擊“取消”按鈕,資源就不會(huì)卸載。

window.addEventListener('beforeunload', function (event) {
  event.returnValue = '你確定離開嗎?';
});

上面代碼中,用戶如果關(guān)閉窗口,瀏覽器會(huì)彈出一個(gè)窗口,要求用戶確認(rèn)。

瀏覽器對(duì)這個(gè)事件的行為很不一致,有的瀏覽器調(diào)用event.preventDefault(),也會(huì)彈出對(duì)話框。IE 瀏覽器需要顯式返回一個(gè)非空的字符串,才會(huì)彈出對(duì)話框。而且,大多數(shù)瀏覽器在對(duì)話框中不顯示指定文本,只顯示默認(rèn)文本。因此,可以采用下面的寫法,取得最大的兼容性。

window.addEventListener('beforeunload', function (e) {
  var confirmationMessage = '確認(rèn)關(guān)閉窗口?';

  e.returnValue = confirmationMessage;
  return confirmationMessage;
});

注意,許多手機(jī)瀏覽器(比如 Safari)默認(rèn)忽略這個(gè)事件,桌面瀏覽器也有辦法忽略這個(gè)事件。所以,它可能根本不會(huì)生效,不能依賴它來阻止用戶關(guān)閉瀏覽器窗口,最好不要使用這個(gè)事件。

另外,一旦使用了beforeunload事件,瀏覽器就不會(huì)緩存當(dāng)前網(wǎng)頁,使用“回退”按鈕將重新向服務(wù)器請(qǐng)求網(wǎng)頁。這是因?yàn)楸O(jiān)聽這個(gè)事件的目的,一般是為了網(wǎng)頁狀態(tài),這時(shí)緩存頁面的初始狀態(tài)就沒意義了。

unload 事件 

unload事件在窗口關(guān)閉或者document對(duì)象將要卸載時(shí)觸發(fā)。它的觸發(fā)順序排在beforeunloadpagehide事件后面。

unload事件發(fā)生時(shí),文檔處于一個(gè)特殊狀態(tài)。所有資源依然存在,但是對(duì)用戶來說都不可見,UI 互動(dòng)全部無效。這個(gè)事件是無法取消的,即使在監(jiān)聽函數(shù)里面拋出錯(cuò)誤,也不能停止文檔的卸載。

window.addEventListener('unload', function(event) {
  console.log('文檔將要卸載');
});

手機(jī)上,瀏覽器或系統(tǒng)可能會(huì)直接丟棄網(wǎng)頁,這時(shí)該事件根本不會(huì)發(fā)生。而且跟beforeunload事件一樣,一旦使用了unload事件,瀏覽器就不會(huì)緩存當(dāng)前網(wǎng)頁,理由同上。因此,任何情況下都不應(yīng)該依賴這個(gè)事件,指定網(wǎng)頁卸載時(shí)要執(zhí)行的代碼,可以考慮完全不使用這個(gè)事件。

該事件可以用pagehide代替。

load 事件,error 事件 

load事件在頁面或某個(gè)資源加載成功時(shí)觸發(fā)。注意,頁面或資源從瀏覽器緩存加載,并不會(huì)觸發(fā)load事件。

window.addEventListener('load', function(event) {
  console.log('所有資源都加載完成');
});

error事件是在頁面或資源加載失敗時(shí)觸發(fā)。abort事件在用戶取消加載時(shí)觸發(fā)。

這三個(gè)事件實(shí)際上屬于進(jìn)度事件,不僅發(fā)生在document對(duì)象,還發(fā)生在各種外部資源上面。瀏覽網(wǎng)頁就是一個(gè)加載各種資源的過程,圖像(image)、樣式表(style sheet)、腳本(script)、視頻(video)、音頻(audio)、Ajax請(qǐng)求(XMLHttpRequest)等等。這些資源和document對(duì)象、window對(duì)象、XMLHttpRequestUpload 對(duì)象,都會(huì)觸發(fā)load事件和error事件。

最后,頁面的load事件也可以用pageshow事件代替。

session 歷史事件 

pageshow 事件,pagehide 事件 

默認(rèn)情況下,瀏覽器會(huì)在當(dāng)前會(huì)話(session)緩存頁面,當(dāng)用戶點(diǎn)擊“前進(jìn)/后退”按鈕時(shí),瀏覽器就會(huì)從緩存中加載頁面。

pageshow事件在頁面加載時(shí)觸發(fā),包括第一次加載和從緩存加載兩種情況。如果要指定頁面每次加載(不管是不是從瀏覽器緩存)時(shí)都運(yùn)行的代碼,可以放在這個(gè)事件的監(jiān)聽函數(shù)。

第一次加載時(shí),它的觸發(fā)順序排在load事件后面。從緩存加載時(shí),load事件不會(huì)觸發(fā),因?yàn)榫W(wǎng)頁在緩存中的樣子通常是load事件的監(jiān)聽函數(shù)運(yùn)行后的樣子,所以不必重復(fù)執(zhí)行。同理,如果是從緩存中加載頁面,網(wǎng)頁內(nèi)初始化的 JavaScript 腳本(比如 DOMContentLoaded 事件的監(jiān)聽函數(shù))也不會(huì)執(zhí)行。

window.addEventListener('pageshow', function(event) {
  console.log('pageshow: ', event);
});

pageshow事件有一個(gè)persisted屬性,返回一個(gè)布爾值。頁面第一次加載時(shí),這個(gè)屬性是false;當(dāng)頁面從緩存加載時(shí),這個(gè)屬性是true

window.addEventListener('pageshow', function(event){
  if (event.persisted) {
    // ...
  }
});

pagehide事件與pageshow事件類似,當(dāng)用戶通過“前進(jìn)/后退”按鈕,離開當(dāng)前頁面時(shí)觸發(fā)。它與 unload 事件的區(qū)別在于,如果在 window 對(duì)象上定義unload事件的監(jiān)聽函數(shù)之后,頁面不會(huì)保存在緩存中,而使用pagehide事件,頁面會(huì)保存在緩存中。

pagehide事件實(shí)例也有一個(gè)persisted屬性,將這個(gè)屬性設(shè)為true,就表示頁面要保存在緩存中;設(shè)為false,表示網(wǎng)頁不保存在緩存中,這時(shí)如果設(shè)置了unload 事件的監(jiān)聽函數(shù),該函數(shù)將在 pagehide 事件后立即運(yùn)行。

如果頁面包含<frame><iframe>元素,則<frame>頁面的pageshow事件和pagehide事件,都會(huì)在主頁面之前觸發(fā)。

注意,這兩個(gè)事件只在瀏覽器的history對(duì)象發(fā)生變化時(shí)觸發(fā),跟網(wǎng)頁是否可見沒有關(guān)系。

popstate 事件 

popstate事件在瀏覽器的history對(duì)象的當(dāng)前記錄發(fā)生顯式切換時(shí)觸發(fā)。注意,調(diào)用history.pushState()history.replaceState(),并不會(huì)觸發(fā)popstate事件。該事件只在用戶在history記錄之間顯式切換時(shí)觸發(fā),比如鼠標(biāo)點(diǎn)擊“后退/前進(jìn)”按鈕,或者在腳本中調(diào)用history.back()history.forward()、history.go()時(shí)觸發(fā)。

該事件對(duì)象有一個(gè)state屬性,保存history.pushState方法和history.replaceState方法為當(dāng)前記錄添加的state對(duì)象。

window.onpopstate = function (event) {
  console.log('state: ' + event.state);
};
history.pushState({page: 1}, 'title 1', '?page=1');
history.pushState({page: 2}, 'title 2', '?page=2');
history.replaceState({page: 3}, 'title 3', '?page=3');
history.back(); // state: {"page":1}
history.back(); // state: null
history.go(2);  // state: {"page":3}

上面代碼中,pushState方法向history添加了兩條記錄,然后replaceState方法替換掉當(dāng)前記錄。因此,連續(xù)兩次back方法,會(huì)讓當(dāng)前條目退回到原始網(wǎng)址,它沒有附帶state對(duì)象,所以事件的state屬性為null,然后前進(jìn)兩條記錄,又回到replaceState方法添加的記錄。

瀏覽器對(duì)于頁面首次加載,是否觸發(fā)popstate事件,處理不一樣,F(xiàn)irefox 不觸發(fā)該事件。

hashchange 事件 

hashchange事件在 URL 的 hash 部分(即#號(hào)后面的部分,包括#號(hào))發(fā)生變化時(shí)觸發(fā)。該事件一般在window對(duì)象上監(jiān)聽。

hashchange的事件實(shí)例具有兩個(gè)特有屬性:oldURL屬性和newURL屬性,分別表示變化前后的完整 URL。

// URL 是 http://www.example.com/
window.addEventListener('hashchange', myFunction);

function myFunction(e) {
  console.log(e.oldURL);
  console.log(e.newURL);
}

location.hash = 'part2';
// http://www.example.com/
// http://www.example.com/#part2

網(wǎng)頁狀態(tài)事件 

DOMContentLoaded 事件 

網(wǎng)頁下載并解析完成以后,瀏覽器就會(huì)在document對(duì)象上觸發(fā) DOMContentLoaded 事件。這時(shí),僅僅完成了網(wǎng)頁的解析(整張頁面的 DOM 生成了),所有外部資源(樣式表、腳本、iframe 等等)可能還沒有下載結(jié)束。也就是說,這個(gè)事件比load事件,發(fā)生時(shí)間早得多。

document.addEventListener('DOMContentLoaded', function (event) {
  console.log('DOM生成');
});

注意,網(wǎng)頁的 JavaScript 腳本是同步執(zhí)行的,腳本一旦發(fā)生堵塞,將推遲觸發(fā)DOMContentLoaded事件。

document.addEventListener('DOMContentLoaded', function (event) {
  console.log('DOM 生成');
});

// 這段代碼會(huì)推遲觸發(fā) DOMContentLoaded 事件
for(var i = 0; i < 1000000000; i++) {
  // ...
}

readystatechange 事件 

readystatechange事件當(dāng) Document 對(duì)象和 XMLHttpRequest 對(duì)象的readyState屬性發(fā)生變化時(shí)觸發(fā)。document.readyState有三個(gè)可能的值:loading(網(wǎng)頁正在加載)、interactive(網(wǎng)頁已經(jīng)解析完成,但是外部資源仍然處在加載狀態(tài))和complete(網(wǎng)頁和所有外部資源已經(jīng)結(jié)束加載,load事件即將觸發(fā))。

document.onreadystatechange = function () {
  if (document.readyState === 'interactive') {
    // ...
  }
}

這個(gè)事件可以看作DOMContentLoaded事件的另一種實(shí)現(xiàn)方法。

窗口事件 

scroll 事件 

scroll事件在文檔或文檔元素滾動(dòng)時(shí)觸發(fā),主要出現(xiàn)在用戶拖動(dòng)滾動(dòng)條。

window.addEventListener('scroll', callback);

該事件會(huì)連續(xù)地大量觸發(fā),所以它的監(jiān)聽函數(shù)之中不應(yīng)該有非常耗費(fèi)計(jì)算的操作。推薦的做法是使用requestAnimationFramesetTimeout控制該事件的觸發(fā)頻率,然后可以結(jié)合customEvent拋出一個(gè)新事件。

(function () {
  var throttle = function (type, name, obj) {
    var obj = obj || window;
    var running = false;
    var func = function () {
      if (running) { return; }
      running = true;
      requestAnimationFrame(function() {
        obj.dispatchEvent(new CustomEvent(name));
        running = false;
      });
    };
    obj.addEventListener(type, func);
  };

  // 將 scroll 事件轉(zhuǎn)為 optimizedScroll 事件
  throttle('scroll', 'optimizedScroll');
})();

window.addEventListener('optimizedScroll', function() {
  console.log('Resource conscious scroll callback!');
});

上面代碼中,throttle()函數(shù)用于控制事件觸發(fā)頻率,它有一個(gè)內(nèi)部函數(shù)func(),每次scroll事件實(shí)際上觸發(fā)的是這個(gè)函數(shù)。func()函數(shù)內(nèi)部使用requestAnimationFrame()方法,保證只有每次頁面重繪時(shí)(每秒60次),才可能會(huì)觸發(fā)optimizedScroll事件,從而實(shí)際上將scroll事件轉(zhuǎn)換為optimizedScroll事件,觸發(fā)頻率被控制在每秒最多60次。

改用setTimeout()方法,可以放置更大的時(shí)間間隔。

(function() {
  window.addEventListener('scroll', scrollThrottler, false);

  var scrollTimeout;
  function scrollThrottler() {
    if (!scrollTimeout) {
      scrollTimeout = setTimeout(function () {
        scrollTimeout = null;
        actualScrollHandler();
      }, 66);
    }
  }

  function actualScrollHandler() {
    // ...
  }
}());

上面代碼中,每次scroll事件都會(huì)執(zhí)行scrollThrottler函數(shù)。該函數(shù)里面有一個(gè)定時(shí)器setTimeout,每66毫秒觸發(fā)一次(每秒15次)真正執(zhí)行的任務(wù)actualScrollHandler。

下面是一個(gè)更一般的throttle函數(shù)的寫法。

function throttle(fn, wait) {
  var time = Date.now();
  return function() {
    if ((time + wait - Date.now()) < 0) {
      fn();
      time = Date.now();
    }
  }
}

window.addEventListener('scroll', throttle(callback, 1000));

上面的代碼將scroll事件的觸發(fā)頻率,限制在一秒一次。

lodash函數(shù)庫提供了現(xiàn)成的throttle函數(shù),可以直接使用。

window.addEventListener('scroll', _.throttle(callback, 1000));

本書前面介紹過debounce的概念,throttle與它區(qū)別在于,throttle是“節(jié)流”,確保一段時(shí)間內(nèi)只執(zhí)行一次,而debounce是“防抖”,要連續(xù)操作結(jié)束后再執(zhí)行。以網(wǎng)頁滾動(dòng)為例,debounce要等到用戶停止?jié)L動(dòng)后才執(zhí)行,throttle則是如果用戶一直在滾動(dòng)網(wǎng)頁,那么在滾動(dòng)過程中還是會(huì)執(zhí)行。

resize 事件 

resize事件在改變?yōu)g覽器窗口大小時(shí)觸發(fā),主要發(fā)生在window對(duì)象上面。

var resizeMethod = function () {
  if (document.body.clientWidth < 768) {
    console.log('移動(dòng)設(shè)備的視口');
  }
};

window.addEventListener('resize', resizeMethod, true);

該事件也會(huì)連續(xù)地大量觸發(fā),所以最好像上面的scroll事件一樣,通過throttle函數(shù)控制事件觸發(fā)頻率。

fullscreenchange 事件,fullscreenerror 事件 

fullscreenchange事件在進(jìn)入或退出全屏狀態(tài)時(shí)觸發(fā),該事件發(fā)生在document對(duì)象上面。

document.addEventListener('fullscreenchange', function (event) {
  console.log(document.fullscreenElement);
});

fullscreenerror事件在瀏覽器無法切換到全屏狀態(tài)時(shí)觸發(fā)。

剪貼板事件 

以下三個(gè)事件屬于剪貼板操作的相關(guān)事件。

  • cut:將選中的內(nèi)容從文檔中移除,加入剪貼板時(shí)觸發(fā)。
  • copy:進(jìn)行復(fù)制動(dòng)作時(shí)觸發(fā)。
  • paste:剪貼板內(nèi)容粘貼到文檔后觸發(fā)。

舉例來說,如果希望禁止輸入框的粘貼事件,可以使用下面的代碼。

inputElement.addEventListener('paste', e => e.preventDefault());

上面的代碼使得用戶無法在<input>輸入框里面粘貼內(nèi)容。

cutcopy、paste這三個(gè)事件的事件對(duì)象都是ClipboardEvent接口的實(shí)例。ClipboardEvent有一個(gè)實(shí)例屬性clipboardData,是一個(gè) DataTransfer 對(duì)象,存放剪貼的數(shù)據(jù)。具體的 API 接口和操作方法,請(qǐng)參見《拖拉事件》的 DataTransfer 對(duì)象部分。

document.addEventListener('copy', function (e) {
  e.clipboardData.setData('text/plain', 'Hello, world!');
  e.clipboardData.setData('text/html', '<b>Hello, world!</b>');
  e.preventDefault();
});

上面的代碼使得復(fù)制進(jìn)入剪貼板的,都是開發(fā)者指定的數(shù)據(jù),而不是用戶想要拷貝的數(shù)據(jù)。

焦點(diǎn)事件 

焦點(diǎn)事件發(fā)生在元素節(jié)點(diǎn)和document對(duì)象上面,與獲得或失去焦點(diǎn)相關(guān)。它主要包括以下四個(gè)事件。

  • focus:元素節(jié)點(diǎn)獲得焦點(diǎn)后觸發(fā),該事件不會(huì)冒泡。
  • blur:元素節(jié)點(diǎn)失去焦點(diǎn)后觸發(fā),該事件不會(huì)冒泡。
  • focusin:元素節(jié)點(diǎn)將要獲得焦點(diǎn)時(shí)觸發(fā),發(fā)生在focus事件之前。該事件會(huì)冒泡。
  • focusout:元素節(jié)點(diǎn)將要失去焦點(diǎn)時(shí)觸發(fā),發(fā)生在blur事件之前。該事件會(huì)冒泡。

這四個(gè)事件的事件對(duì)象都繼承了FocusEvent接口。FocusEvent實(shí)例具有以下屬性。

  • FocusEvent.target:事件的目標(biāo)節(jié)點(diǎn)。
  • FocusEvent.relatedTarget:對(duì)于focusin事件,返回失去焦點(diǎn)的節(jié)點(diǎn);對(duì)于focusout事件,返回將要接受焦點(diǎn)的節(jié)點(diǎn);對(duì)于focusblur事件,返回null。

由于focusblur事件不會(huì)冒泡,只能在捕獲階段觸發(fā),所以addEventListener方法的第三個(gè)參數(shù)需要設(shè)為true。

form.addEventListener('focus', function (event) {
  event.target.style.background = 'pink';
}, true);

form.addEventListener('blur', function (event) {
  event.target.style.background = '';
}, true);

上面代碼針對(duì)表單的文本輸入框,接受焦點(diǎn)時(shí)設(shè)置背景色,失去焦點(diǎn)時(shí)去除背景色。

CustomEvent 接口 

CustomEvent 接口用于生成自定義的事件實(shí)例。那些瀏覽器預(yù)定義的事件,雖然可以手動(dòng)生成,但是往往不能在事件上綁定數(shù)據(jù)。如果需要在觸發(fā)事件的同時(shí),傳入指定的數(shù)據(jù),就可以使用 CustomEvent 接口生成的自定義事件對(duì)象。

瀏覽器原生提供CustomEvent()構(gòu)造函數(shù),用來生成 CustomEvent 事件實(shí)例。

new CustomEvent(type, options)

CustomEvent()構(gòu)造函數(shù)接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是字符串,表示事件的名字,這是必須的。第二個(gè)參數(shù)是事件的配置對(duì)象,這個(gè)參數(shù)是可選的。CustomEvent的配置對(duì)象除了接受 Event 事件的配置屬性,只有一個(gè)自己的屬性。

  • detail:表示事件的附帶數(shù)據(jù),默認(rèn)為null。

下面是一個(gè)例子。

var event = new CustomEvent('build', { 'detail': 'hello' });

function eventHandler(e) {
  console.log(e.detail);
}

document.body.addEventListener('build', function (e) {
  console.log(e.detail);
});

document.body.dispatchEvent(event);

上面代碼中,我們手動(dòng)定義了build事件。該事件觸發(fā)后,會(huì)被監(jiān)聽到,從而輸出該事件實(shí)例的detail屬性(即字符串hello)。

下面是另一個(gè)例子。

var myEvent = new CustomEvent('myevent', {
  detail: {
    foo: 'bar'
  },
  bubbles: true,
  cancelable: false
});

el.addEventListener('myevent', function (event) {
  console.log('Hello ' + event.detail.foo);
});

el.dispatchEvent(myEvent);

上面代碼也說明,CustomEvent 的事件實(shí)例,除了具有 Event 接口的實(shí)例屬性,還具有detail屬性。


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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)